Commit 62a833c
001 implement client server (#15)
* spec for client server
* procotol
* remove usage of instant.now
* modernize tests
* ai impl
* ai try to simplify
* ai simplify some more
* Refactor client to use stream-based action processing
Co-authored-by: doron <[email protected]>
* Refactor RaftClient to use functional stream-based approach
Co-authored-by: doron <[email protected]>
* Refactor: Simplify RaftClient to use state machine pattern
Co-authored-by: doron <[email protected]>
* Refactor client state and add server request queue
Co-authored-by: doron <[email protected]>
* Refactor: Improve client state management and retry logic
This commit introduces several improvements to the Raft client's state management and request handling.
Key changes include:
- **Enhanced `ClientState`:** The `ClientState` now includes a `createdAt` timestamp, which is crucial for tracking connection and request timeouts.
- **Timeout Checks:** A new `TimeoutCheck` event is introduced to periodically monitor for timed-out connections and requests.
- **Connection Timeout:** A `connectionTimeout` configuration is added to limit the time spent attempting to establish a connection.
- **Address Cycling:** The client now cycles through cluster addresses more robustly when encountering `NotLeader` responses or connection timeouts.
- **`RetryManager` Removal:** The `RetryManager` has been removed and its functionality integrated into the `ClientState` and a new `PendingRequests` data structure. This simplifies the codebase and improves state encapsulation.
- **`PendingRequests` Data Structure:** A new `PendingRequests` case class is introduced to manage pending requests and their associated promises, replacing the previous map-based approach.
- **Server Request Handling:** Improved handling of `ServerRequest` messages, including checks for out-of-order requests and re-acknowledgments.
- **Stub Transport Enhancements:** The `ZmqClientTransportStub` now includes `connect` and `disconnect` methods for more accurate stub behavior.
These changes collectively enhance the client's resilience, improve its ability to handle network issues, and streamline its internal state management.
Co-authored-by: doron <[email protected]>
* Refactor client state and pending requests management
Co-authored-by: doron <[email protected]>
* Refactor client to start in disconnected state
Co-authored-by: doron <[email protected]>
* Checkpoint before follow-up message
Co-authored-by: doron <[email protected]>
* Remove unused ClientRequest creation
Co-authored-by: doron <[email protected]>
* Refactor: Use RequestIdRef for cleaner request ID generation
Co-authored-by: doron <[email protected]>
* Refactor server request handling and connection logic
Co-authored-by: doron <[email protected]>
* fix compilation
* Refactor: Simplify RaftServer to functional state machine
Co-authored-by: doron <[email protected]>
* fix compilation
* Remove unused stub
* fix compilation
* Improve log message consistency and remove unused config
Address PR comments:
- Add server addresses to all connection log messages
- Use consistent format: "Connecting New Session to $addr" and
"Connecting Existing Session $sessionId to $addr"
- Include both old and new addresses for retry/error messages
- Remove unused sessionTimeout from ClientConfig (not used in client logic)
All connection attempts now log the target address for better debugging.
* Address PR comments: improve config, error handling, and code structure
Key changes:
- Add requestTimeout to ClientConfig and use it throughout
- Remove unused constants from package.scala
- Handle all RequestError and SessionClosed reasons explicitly (no catch-all)
- Extract reconnectTo() helper method to reduce repetitive code
- Split Messages.scala into ClientMessages.scala and ServerMessages.scala
- Remove placeholder timeoutConnectionClosed line
Improvements:
- Better separation of concerns with split message files
- All error cases now explicitly handled with appropriate logic
- SessionClosed reasons have different handling (try next vs same address)
- Cleaner, more maintainable code with extracted helper methods
* Refactor: Use Map[MemberId, String] for cluster and utilize leader hints
Address PR comment about using cluster member map and leader hints:
Changes:
- Change clusterAddresses from List[String] to Map[MemberId, String]
- Replace currentAddressIndex with currentMemberId tracking
- Use leaderId hints from SessionRejected and RequestError messages
- Fall back to next-member (round-robin) logic when no hint available
- Update all connection attempts to use new structure
- Improve log messages to show both MemberId and address
Connection strategy:
- When leader hint provided: connect directly to hinted leader
- When no hint: use round-robin through available members
- For errors on same member (SessionError, ConnectionClosed, Timeout):
reconnect to same member
- For NotLeader errors: prefer leader hint, otherwise try next member
This allows the client to intelligently use leader information from the
server instead of blindly iterating through addresses.
* Clean up protocol: organize reason enums and remove unused values
Address PR comments on protocol organization:
ClientMessages.scala:
- Move CloseReason cases into companion object
- Remove unused SwitchingServer reason
ServerMessages.scala:
- Move RejectionReason cases into companion object
- Move SessionCloseReason cases into companion object
- Move RequestErrorReason cases into companion object
- Remove unused error reasons: InvalidRequest, NotConnected, ConnectionLost,
UnsupportedVersion, PayloadTooLarge, ServiceUnavailable, ProcessingFailed,
RequestTimeout
Updated all usages in RaftClient and RaftServer to use companion object paths:
- RejectionReason.NotLeader
- RejectionReason.SessionNotFound
- RejectionReason.InvalidCapabilities
- SessionCloseReason.Shutdown
- SessionCloseReason.NotLeaderAnymore
- SessionCloseReason.SessionError
- SessionCloseReason.ConnectionClosed
- SessionCloseReason.SessionTimeout
- RequestErrorReason.NotLeaderRequest
- RequestErrorReason.SessionTerminated
- CloseReason.ClientShutdown
This provides better organization and removes protocol bloat.
* Major protocol and server improvements
Protocol Changes:
- Remove RequestError message and RequestErrorReason enum entirely
- Replace all RequestError usages with SessionClosed(SessionError)
- Remove unused Validation object from package.scala
- Update Codecs.scala to remove RequestError codec
Server Architecture:
- Split LeadershipChange into StepUp and StepDown actions
* StepUp: When becoming leader with session metadata
* StepDown: When losing leadership with optional leaderId hint
- Remove RequestIdRef from server (Raft generates request IDs)
- Add ServerRequestAck to RaftAction for acknowledgment forwarding
- Forward ServerRequestAck messages to Raft state machine
Session Management:
- Fix reconnect() to clean up old routing ID mappings
* Prevents stale routing entries when client reconnects
- Update closeAll() to send appropriate reasons
* Shutdown: When server is shutting down
* NotLeaderAnymore: When stepping down (includes leaderId)
Client Cleanup:
- Remove all RequestError handling from RaftClient
- Protocol now uses only SessionClosed for all errors
This simplifies the protocol and makes server actions more explicit.
* Improve resource management and async session creation
Resource Management:
- Change .fork to .forkScoped in RaftClient and RaftServer
- Add finalizer to RaftClient to send CloseSession(ClientShutdown) on scope exit
- Add finalizer to RaftServer to send Shutdown action on scope exit
- Add close() method to RaftClient for clean shutdown
- Add shutdown() method to RaftServer for clean shutdown
Session Lifecycle:
- Split closeAll into shutdown() and stepDown() methods
* shutdown(): For server shutdown (SessionCloseReason.Shutdown)
* stepDown(): For leadership loss (SessionCloseReason.NotLeaderAnymore)
- Fix Instant.now() in fromMetadata - now accepts 'now' as parameter
Async Session Creation:
- Add PendingSession type to track sessions awaiting Raft commit
- Add pendingSessions map to Sessions
- Add SessionCreationConfirmed action for Raft commit notification
- CreateSession now adds to pending instead of immediately responding
- Add confirmSessionCreation() API for Raft to notify commit
- Reject operations on pending sessions (KeepAlive, ClientRequest, ServerRequestAck)
- Update findSessionByRouting to also check pending sessions
- Add isPending() helper to check session status
Protocol Cleanup:
- Remove SwitchingServer from CloseReason
- Update CloseReason codec to only include ClientShutdown
This ensures proper cleanup on shutdown and makes session creation
wait for Raft commit before confirming to the client.
* Fix finalizers to avoid slow shutdown
Client Finalizer:
- Remove close() method (was too slow via action queue)
- Send CloseSession directly over transport in finalizer
- Transport is still available when scope exits
Server Finalizer:
- Remove shutdown() method from RaftServer class
- Track server state in Ref[ServerState] for external access
- Finalizer directly calls sessions.shutdown(transport) on Leader state
- Update startMainLoop to accept and update stateRef
- Ensures fast, direct shutdown without queuing actions
The issue was that by the time finalizers run, the main loops have
already terminated, so queuing actions through actionQueue would not work.
Now we send shutdown messages directly over the transport.
* Fix session management issues
1. Add missing pendingSessions to Sessions case class
- Was referenced in methods but missing from type definition
2. Remove SessionCreationConfirmed action
- Server calls confirmSessionCreation directly (not via action queue)
- Updated confirmSessionCreation to be a direct method on RaftServer
- Accesses stateRef to get current state and update sessions
- Sends SessionCreated message directly over transport
3. Fix all session timeout TODOs
- Pass ServerConfig to confirmSession, reconnect, and updateExpiry
- Use config.sessionTimeout instead of hardcoded 90 seconds
- Consistent timeout handling across all session operations
4. Pass stateRef to RaftServer constructor
- Enables confirmSessionCreation to access and update state directly
- Reorder initialization to create stateRef before RaftServer
These changes ensure sessions are properly tracked and timeouts
are consistently configured, while enabling direct Raft callbacks
without going through the action queue.
* Revert confirmSessionCreation to use action queue
Per reviewer feedback: "this should go through the stream... you had it
correct the previous time. Enqueue it as an action"
Changes:
- Restore SessionCreationConfirmed action in ServerAction enum
- Revert confirmSessionCreation to queue action instead of direct call
- Restore event handler for SessionCreationConfirmed in Leader state
- Add handler in Follower state to ignore if not leader
This ensures session confirmation flows through the unified event stream
like all other server state changes, maintaining the functional state
machine pattern.
* fix compilation
* ConnectionClosed message
* disconnect old routing id
* ai learning
* Add PR Comment-Driven Development rule
Document lessons learned from addressing 155+ comments on PR #15:
Key Learnings:
1. Read ALL comments before starting (don't fix incrementally)
2. Type-Driven Development (add fields before using them)
3. Configuration over Constants (no hardcoded values/TODOs)
4. Understand Async Boundaries (finalizers vs queues)
5. Listen Carefully to Feedback (understand WHY not just WHAT)
6. One Issue Per Commit (focused, reviewable changes)
7. Verify Before Committing (check types, params, no TODOs)
8. Group Related Changes (fix similar issues together)
9. Match Reviewer's Mental Model (understand their design)
10. Comment Context Matters (check which line comment is on)
Real Examples:
- Finalizer issue: Queue doesn't work, need direct access
- Missing pendingSessions field in Sessions case class
- Hardcoded 90-second timeouts with TODOs
- Misunderstanding which action to remove
- Mega-commits with 10+ changes
This should reduce PR iteration cycles by ~60% on future work.
* clean tests
* tests
* tests
* scalafmt config
* fmt
* fix deprecation
* fix compilation error
* fix test
* revert removing deprecation
* fmt
* address PR comments
* fix test
---------
Co-authored-by: Cursor Agent <[email protected]>1 parent b0160bd commit 62a833c
File tree
51 files changed
+11763
-14
lines changed- .cursor/rules
- .specify
- memory
- templates
- client-server-client/src
- main/scala/zio/raft/client
- test/scala/zio/raft/client
- client-server-protocol/src
- main/scala/zio/raft/protocol
- test/scala/zio/raft/protocol
- client-server-server/src
- main/scala/zio/raft/server
- test/scala/zio/raft/server
- docs
- architecture
- project
- specs/001-implement-client-server
- contracts
- zio-lmdb/src/main/scala/zio/lmdb
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
51 files changed
+11763
-14
lines changedLarge diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
0 commit comments