Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 51 additions & 46 deletions topic/src/main/java/tech/ydb/topic/impl/TopicRetryableStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@

public abstract class TopicRetryableStream<R extends Message, W extends Message, S extends TopicStream<R, W>> {
protected final String debugId;

private final Logger logger;
private final RetryConfig retryConfig;
private final ScheduledExecutorService scheduler;

private final AtomicReference<State> state = new AtomicReference<>();
private final AtomicReference<State> state = new AtomicReference<>(null);
private final AtomicInteger streamCount = new AtomicInteger(0);
private final RetryState retryState = new RetryState();

Expand Down Expand Up @@ -71,14 +72,13 @@ public void start() {

if (!state.compareAndSet(null, newState)) {
logger.warn("[{}] double start of stream, skipping", debugId);
newState.closeWithoutStart();
newState.close();
return;
}

newState.start();

if (isClosed) {
close();
newState.close();
}
}

Expand All @@ -94,29 +94,32 @@ public void fail(Status status) {
State local = state.getAndSet(null);
if (local != null) {
logger.warn("[{}] failed by application-side error {}", debugId, status);
onStreamStop(local.closeStream(), status, retryConfig.getStatusRetryPolicy(status));
local.close();
onStreamStop(local.stream, status, retryConfig.getStatusRetryPolicy(status));
}
}

public void send(W msg) {
State local = state.get();
S stream = local != null ? local.stream.get() : null;
if (stream == null) {
if (isClosed || local == null) {
logger.warn("[{}] send message before starting or after closing", debugId);
Comment thread
alex268 marked this conversation as resolved.
return;
}
if (local.stream == null) {
logger.warn("[{}] send message before stream is ready", debugId);
return;
}
stream.send(msg);

local.stream.send(msg);
}

public boolean close() {
isClosed = true;
State local = state.getAndSet(null);
if (local == null) {
return false;
State local = state.get();
if (local != null) {
local.close();
Comment thread
alex268 marked this conversation as resolved.
}

onStreamStop(local.closeStream(), Status.SUCCESS, null);
return true;
return local != null;
}

private void onStreamStop(S closed, Status status, RetryPolicy policy) {
Expand Down Expand Up @@ -163,72 +166,74 @@ private void onStreamStop(S closed, Status status, RetryPolicy policy) {

private class State {
private final CompletableFuture<Result<S>> future;
private final AtomicReference<S> stream = new AtomicReference<>();
private final S stream;

State(CompletableFuture<Result<S>> future) {
this(future, null);
}

State(CompletableFuture<Result<S>> future, S stream) {
this.future = future;
this.stream = stream;
}

public void start() {
this.future.whenComplete((res, th) -> {
if (res == null) {
if (state.compareAndSet(this, null)) {
Status wrapped = Status.of(StatusCode.CLIENT_INTERNAL_ERROR, th);
onStreamStop(null, wrapped, retryConfig.getThrowableRetryPolicy(th));
if (res == null || !res.isSuccess()) {
if (!state.compareAndSet(this, null)) {
return;
}
return;
}

if (!res.isSuccess()) {
if (state.compareAndSet(this, null)) {
if (res != null) {
onStreamStop(null, res.getStatus(), retryConfig.getStatusRetryPolicy(res.getStatus()));
} else {
Status wrapped = Status.of(StatusCode.CLIENT_INTERNAL_ERROR, th);
onStreamStop(null, wrapped, retryConfig.getThrowableRetryPolicy(th));
}
return;
}

startStream(res.getValue());
State next = new State(future, res.getValue());
if (state.compareAndSet(this, next)) {
next.startStream();
}
});
}

public void closeWithoutStart() {
public boolean close() {
if (stream != null) {
stream.close();
return true;
}

boolean effective = state.compareAndSet(this, null);
this.future.whenComplete((res, th) -> {
if (res != null && res.isSuccess()) {
res.getValue().close();
}
});
}

private void startStream(S local) {
if (state.get() != this) {
local.close();
return;
if (effective) {
onStreamStop(null, Status.SUCCESS, retryConfig.getStatusRetryPolicy(Status.SUCCESS));
return true;
}

stream.set(local);
local.start(msg -> onNext(local, msg)).whenComplete((status, th) -> {
return false;
}

private void startStream() {
stream.start(msg -> onNext(stream, msg)).whenComplete((status, th) -> {
if (!state.compareAndSet(this, null)) {
return;
}

if (status != null) {
onStreamStop(local, status, retryConfig.getStatusRetryPolicy(status));
onStreamStop(stream, status, retryConfig.getStatusRetryPolicy(status));
} else {
Status wrapped = Status.of(StatusCode.CLIENT_INTERNAL_ERROR, th);
onStreamStop(local, wrapped, retryConfig.getThrowableRetryPolicy(th));
onStreamStop(stream, wrapped, retryConfig.getThrowableRetryPolicy(th));
}
});

if (state.get() != this) {
closeStream();
}
}

public S closeStream() {
S local = stream.getAndSet(null);
if (local != null) {
local.close();
}
return local;
}
}

Expand Down
51 changes: 33 additions & 18 deletions topic/src/main/java/tech/ydb/topic/impl/TopicStreamBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

import com.google.protobuf.Message;
Expand All @@ -14,16 +15,15 @@
public abstract class TopicStreamBase<R extends Message, W extends Message> implements TopicStream<R, W> {
private final Logger logger;
private final String debugId;
private final GrpcReadWriteStream<R, W> stream;
private final W initRequest;
private final CompletableFuture<Status> streamStatus = new CompletableFuture<>();
private final AtomicReference<GrpcReadWriteStream<R, W>> ref;
private volatile String token;
private volatile boolean isStopped = false;

public TopicStreamBase(Logger logger, String debugId, GrpcReadWriteStream<R, W> stream, W initRequest) {
this.logger = logger;
this.debugId = debugId;
this.stream = stream;
this.ref = new AtomicReference<>(stream);
this.initRequest = initRequest;
this.token = stream.authToken();
}
Expand All @@ -34,50 +34,65 @@ public TopicStreamBase(Logger logger, String debugId, GrpcReadWriteStream<R, W>
@Override
public CompletableFuture<Status> start(Consumer<R> messageHandler) {
this.logger.debug("[{}] is about to start", debugId);
this.stream.start((R msg) -> {
GrpcReadWriteStream<R, W> stream = ref.get();
if (stream == null) {
return CompletableFuture.completedFuture(Status.of(StatusCode.CLIENT_INTERNAL_ERROR));
}

stream.start((R msg) -> {
Status messageStatus = parseMessageStatus(msg);
if (messageStatus.isSuccess()) {
messageHandler.accept(msg);
} else {
logger.warn("[{}] stopped by getting status {}", debugId, messageStatus);
if (streamStatus.complete(messageStatus)) {
stream.close();
}
preDestroy();
streamStatus.complete(messageStatus);
closeStream();
}
}).whenComplete((st, th) -> {
isStopped = true;
ref.set(null);
Status status = st != null ? st : Status.of(StatusCode.CLIENT_INTERNAL_ERROR, th);
logger.debug("[{}] finished with status {}", debugId, status);
preDestroy();
streamStatus.complete(status);
});

if (!streamStatus.isDone()) {
stream.sendNext(initRequest);
GrpcReadWriteStream<R, W> started = ref.get();
if (started != null) {
started.sendNext(initRequest);
}

return streamStatus;
}

@Override
public void close() {
logger.debug("[{}] closed by app", debugId);
if (!streamStatus.isDone()) {
isStopped = true;
preDestroy();
closeStream();
Comment thread
alex268 marked this conversation as resolved.
}

protected void preDestroy() {
// nothing
}

private void closeStream() {
GrpcReadWriteStream<R, W> stream = ref.getAndSet(null);
if (stream != null) {
stream.close();
}
}

protected void fail(Status status) {
logger.warn("[{}] stopped by fail {}", debugId, status);
if (streamStatus.complete(status)) {
isStopped = true;
stream.close();
}
preDestroy();
streamStatus.complete(status);
closeStream();
}

@Override
public void send(W req) {
if (isStopped) {
GrpcReadWriteStream<R, W> stream = ref.get();
if (stream == null) {
logger.warn("[{}] is already closed. Next message with type {} was NOT sent", debugId,
Comment thread
alex268 marked this conversation as resolved.
req.getDescriptorForType().getName());
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public CompletableFuture<Void> shutdown() {
// implicit closing because stream will never call onClose
close(Status.SUCCESS.withIssues(Issue.of("Closed by client", Issue.Severity.INFO)));
}
initFuture.completeExceptionally(new RuntimeException("Reader closed by user"));
Comment thread
alex268 marked this conversation as resolved.
return shutdownFuture;
}

Expand Down
9 changes: 7 additions & 2 deletions topic/src/main/java/tech/ydb/topic/read/impl/ReadSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@ Handler getHandler() {
return handler;
}

public void closeAll() {
@Override
protected void preDestroy() {
isClosed = true;
decoder.stop();
locks.clear();
partitions.values().forEach(ReadPartition::close);
Comment thread
alex268 marked this conversation as resolved.
partitions.clear();
config.getMetrics().unregister();
}

public boolean commitOffsets(PartitionSession session, List<OffsetsRange> rangesToCommit) {
Expand Down Expand Up @@ -213,6 +213,11 @@ public void onStartPartition(YdbTopic.StreamReadMessage.StartPartitionSessionReq
return;
}

if (isClosed) {
preDestroy();
return;
}

partition.start(committed, offsets);
TopicPartition lockKey = new TopicPartition(ps);
if (locks.computeIfPresent(lockKey, (key, lock) -> lock.bind(partition)) != null) {
Expand Down
8 changes: 2 additions & 6 deletions topic/src/main/java/tech/ydb/topic/read/impl/ReaderImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ protected void onRetry(ReadSession stream, Status status) {
logger.error("[{}] errorHandler onRetry processing throws exception", debugId, ex);
}
}
if (stream != null) {
stream.closeAll();
}
config.getMetrics().unregister();
}

@Override
Expand All @@ -96,9 +94,7 @@ protected void onClose(ReadSession stream, Status status) {
logger.error("[{}] errorHandler onClose processing throws exception", debugId, ex);
}
}
if (stream != null) {
stream.closeAll();
}
config.getMetrics().unregister();
onReaderClosed(status);
}

Expand Down
Loading
Loading