RATIS-2681. Add a listener for gRPC log appender lifecycle - #1577
Conversation
f54e9ac to
0c9ef3d
Compare
0c9ef3d to
d7fea1b
Compare
| */ | ||
| @Override | ||
| public void onError(Throwable t) { | ||
| if (!isRunning()) { |
There was a problem hiding this comment.
I have one concern about AppendLogResponseHandler.onError().
When the log appender is no longer running, the method returns before clearing and reporting the pending requests:
if (!isRunning()) {
return;
}
If a shutdown or leader transition races with a gRPC stream error, some already-sent data-bearing requests may never receive a terminal FAILURE event.
This seems inconsistent with the requirement that every transfer attempt should generate exactly one terminal event. The snapshot error path already reports the failure before checking isRunning().
Could we report the affected sent requests as failures before returning, without updating the Raft replication state?
It would also be helpful to add a regression test covering a stream failure with multiple pending data-bearing requests.
There was a problem hiding this comment.
Addressed in the updated implementation. A stopped AppendLogResponseHandler now emits onAppendEntriesReset(error) before returning, without resetting the client or updating follower/leader state. The regression test covers both appender stop and leadership change with multiple pending requests, including a throwing listener.
The extension now exposes generic per-appender lifecycle callbacks. Consumers correlate request IDs and deduplicate terminal outcomes; audit-specific filtering and aggregation are outside Ratis. The snapshot error path also releases its waiter when the appender has stopped. All 16 targeted tests pass.
szetszwo
left a comment
There was a problem hiding this comment.
@HTHou , thanks for working on this!
- Let's implement appendEntries listener first to make this change smaller.
- Let's add sub-interfaces for AppendEntries (and InstallSnapshot later).
- Reorganize the methods so each method only be called once. I think it will be easier to understand the methods. Please take a look.
See https://issues.apache.org/jira/secure/attachment/13084350/1577_review.patch
| public GrpcLogAppender(RaftServer.Division server, LeaderState leaderState, FollowerInfo f) { | ||
| this(server, leaderState, f, null); | ||
| } |
There was a problem hiding this comment.
It becomes unused. Let's remove it.
There was a problem hiding this comment.
Removed the unused three-argument constructor in 80b96f6.
| try { | ||
| listener = logAppenderListenerFactory.create(server.getMemberId(), f.getPeer()); | ||
| } catch (Throwable t) { | ||
| LOG.warn("Failed to create gRPC log appender listener", t); |
There was a problem hiding this comment.
Let's include the ids:
LOG.warn("{}: Failed to create gRPC log appender listener for {}",
server.getMemberId(), f.getPeer().getId(), t);There was a problem hiding this comment.
Updated in 80b96f6: the warning now includes the source group member and destination peer IDs.
|
Thanks @szetszwo! Updated in 80b96f6.
22 targeted tests, Checkstyle, SpotBugs and RAT pass locally. |
|
|
||
| private void updateNextIndex(long replyNextIndex) { | ||
| try (AutoCloseableLock writeLock = lock.writeLock(caller, LOG::trace)) { | ||
| notifyReset("AppendEntries INCONSISTENCY", null); |
There was a problem hiding this comment.
How about adding onReplyInconsistency for INCONSISTENCY reply? Reset client and INCONSISTENCY should be treated differently.
notifyAppendEntriesListener(GrpcLogAppenderListener.AppendEntries::onReplyInconsistency);There was a problem hiding this comment.
Agreed. Updated in 7c6604c: added AppendEntries.onReplyInconsistency() at the pending-request clear in updateNextIndex, and renamed the parent callback to onResetClient. INCONSISTENCY handling no longer emits a client-reset notification. The new callback follows onReply for that response and runs under the existing index/queue write lock. The regression verifies the callback order and that the two events remain separate.
| public void onNext(AppendEntriesReplyProto reply) { | ||
| AppendEntriesRequest request = pendingRequests.remove(reply); | ||
| final AppendEntriesRequest request; | ||
| try (AutoCloseableLock writeLock = lock.writeLock(caller, LOG::trace)) { |
There was a problem hiding this comment.
Question: Why acquiring writeLock here?
There was a problem hiding this comment.
The extra write lock was intended to serialize reply notifications with reset notifications, not to protect ConcurrentHashMap.remove(). I have removed it in 7c6604c and updated the listener contract: replies may race with reset/inconsistency and other terminal callbacks, so consumers handle correlation and deduplication. A regression now holds the appender write lock on one thread while a SUCCESS reply is processed and reported on another thread. All 24 targeted tests and Checkstyle/SpotBugs/RAT checks pass locally on JDK 25.
What changes were proposed in this pull request?
Add an optional factory in
GrpcConfigKeys.Serverthat creates aGrpcLogAppenderListenerfor each source group member and destination peer.This PR is scoped to AppendEntries. A nested
AppendEntriesinterface exposes request registration, received replies, INCONSISTENCY handling, local send failures, timeouts, response-stream errors and completion. The parent listener observes client resets and run-loop exit. InstallSnapshot observation is deferred to a separate change.Each callback has a single invocation site. Request registration, client-reset and reply-inconsistency notifications use the existing appender write lock. Reply notifications do not acquire that lock and may race with resets or other terminal callbacks; consumers correlate requests and deduplicate outcomes.
AppendEntries.onReplyInconsistencyis separate fromonResetClient: it is invoked afteronReplyfor the inconsistent response, immediately before clearing pending requests, without resetting the client. Run-loop exit is reported in a finally block, including exceptional exits. Stream terminal callbacks are still delivered after appender stop or leadership change without updating replication state.Ratis exposes lifecycle information only. Applications own message filtering, correlation, outcome interpretation, protection-method attribution and persistence. Factory, sub-listener accessor and callback exceptions are isolated. Callbacks must return promptly and must not retain payloads.
What is the link to the Apache JIRA
https://issues.apache.org/jira/browse/RATIS-2681
How was this patch tested?
./mvnw -o -pl ratis-test -am test checkstyle:check '-Dtest=TestGrpcLogAppenderCallbacks,TestGrpcLogAppenderListener,TestConfUtils,TestLogAppenderWithGrpc#testLogAppenderAutoRestartOnException,TestWatchRequestWithGrpc#testWatchRequestAsyncChangeLeader' -Dsurefire.failIfNoSpecifiedTests=false./mvnw -o -pl ratis-grpc -am -DskipTests -Djacoco.skip spotbugs:check apache-rat:checkAll 24 targeted tests pass locally on JDK 25: 16 listener tests, 6 configuration tests, and the existing automatic-appender-restart and Watch leader-change regressions. Coverage includes real three-peer replication with an injected send failure and successful retries, stream creation failures, timeouts, late replies, INCONSISTENCY callback ordering and separation from client reset, reply processing while another thread holds the appender write lock, factory/accessor/callback exception isolation, normal/exceptional run-loop exit, and stream callbacks after appender stop or leadership change. Checkstyle, SpotBugs and license checks pass.