@@ -51,14 +51,24 @@ class RaftClient private (
5151 def connect (): UIO [Unit ] =
5252 actionQueue.offer(ClientAction .Connect ).unit
5353
54- def submitCommand (payload : ByteVector ): Task [ByteVector ] =
54+ def submitCommand (payload : ByteVector ): UIO [ByteVector ] =
5555 for {
56- promise <- Promise .make[Throwable , ByteVector ]
56+ promise <- Promise .make[Nothing , ByteVector ]
5757 action = ClientAction .SubmitCommand (payload, promise)
5858 _ <- actionQueue.offer(action)
5959 result <- promise.await
6060 } yield result
6161
62+ /** Submit a read-only Query (leader-served, linearizable).
63+ */
64+ def query (payload : ByteVector ): UIO [ByteVector ] =
65+ for {
66+ promise <- Promise .make[Nothing , ByteVector ]
67+ action = ClientAction .SubmitQuery (payload, promise)
68+ _ <- actionQueue.offer(action)
69+ result <- promise.await
70+ } yield result
71+
6272 def disconnect (): UIO [Unit ] =
6373 actionQueue.offer(ClientAction .Disconnect ).unit
6474
@@ -146,14 +156,18 @@ object RaftClient {
146156 currentMemberId = memberId,
147157 createdAt = now,
148158 nextRequestId = nextRequestId,
149- pendingRequests = PendingRequests .empty
159+ pendingRequests = PendingRequests .empty,
160+ pendingQueries = PendingQueries .empty
150161 )
151162
152163 case StreamEvent .ServerMsg (message) =>
153164 ZIO .logWarning(s " Received message while disconnected: ${message.getClass.getSimpleName}" ).as(this )
154165
155166 case StreamEvent .Action (ClientAction .SubmitCommand (_, promise)) =>
156- promise.fail(new IllegalStateException (" Not connected" )).ignore.as(this )
167+ promise.die(new IllegalStateException (" Not connected" )).ignore.as(this )
168+
169+ case StreamEvent .Action (ClientAction .SubmitQuery (_, promise)) =>
170+ promise.die(new IllegalStateException (" Not connected" )).ignore.as(this )
157171
158172 case StreamEvent .Action (ClientAction .Disconnect ) =>
159173 ZIO .succeed(this )
@@ -173,7 +187,8 @@ object RaftClient {
173187 currentMemberId : MemberId ,
174188 createdAt : Instant ,
175189 nextRequestId : RequestIdRef ,
176- pendingRequests : PendingRequests
190+ pendingRequests : PendingRequests ,
191+ pendingQueries : PendingQueries
177192 ) extends ClientState {
178193 override def stateName : String = " ConnectingNewSession"
179194
@@ -210,8 +225,9 @@ object RaftClient {
210225 for {
211226 _ <- ZIO .logInfo(s " Session created: $sessionId" )
212227 now <- Clock .instant
213- // Send all pending requests
228+ // Send all pending requests/queries
214229 updatedPending <- pendingRequests.resendAll(transport)
230+ updatedPendingQueries <- pendingQueries.resendAll(transport)
215231 } yield Connected (
216232 config = config,
217233 sessionId = sessionId,
@@ -220,6 +236,7 @@ object RaftClient {
220236 serverRequestTracker = ServerRequestTracker (),
221237 nextRequestId = nextRequestId,
222238 pendingRequests = updatedPending,
239+ pendingQueries = updatedPendingQueries,
223240 currentMemberId = currentMemberId
224241 )
225242 } else {
@@ -249,6 +266,14 @@ object RaftClient {
249266 now <- Clock .instant
250267 newPending = pendingRequests.add(requestId, payload, promise, now)
251268 } yield copy(pendingRequests = newPending)
269+
270+ case StreamEvent .Action (ClientAction .SubmitQuery (payload, promise)) =>
271+ // Queue query while connecting (handled after session established)
272+ for {
273+ now <- Clock .instant
274+ correlationId <- Random .nextUUID.map(u => CorrelationId .fromString(u.toString))
275+ newPending = pendingQueries.add(correlationId, payload, promise, now)
276+ } yield copy(pendingQueries = newPending)
252277
253278 case StreamEvent .TimeoutCheck =>
254279 for {
@@ -268,6 +293,9 @@ object RaftClient {
268293 case StreamEvent .ServerMsg (ClientResponse (_, _)) =>
269294 ZIO .logWarning(" Received ClientResponse while connecting, ignoring" ).as(this )
270295
296+ case StreamEvent .ServerMsg (QueryResponse (correlationId, result)) =>
297+ ZIO .logWarning(" Received QueryResponse while connecting, ignoring" ).as(this )
298+
271299 case StreamEvent .ServerMsg (_ : RequestError ) =>
272300 ZIO .logWarning(" Received RequestError while connecting, ignoring" ).as(this )
273301
@@ -305,7 +333,8 @@ object RaftClient {
305333 createdAt : Instant ,
306334 serverRequestTracker : ServerRequestTracker ,
307335 nextRequestId : RequestIdRef ,
308- pendingRequests : PendingRequests
336+ pendingRequests : PendingRequests ,
337+ pendingQueries : PendingQueries
309338 ) extends ClientState {
310339 override def stateName : String = s " ConnectingExistingSession( $sessionId) "
311340
@@ -345,8 +374,9 @@ object RaftClient {
345374 s " Session continued: $sessionId at $currentMemberId ( ${currentAddr.getOrElse(" unknown" )}) "
346375 )
347376 now <- Clock .instant
348- // Send all pending requests
377+ // Send all pending requests/queries
349378 updatedPending <- pendingRequests.resendAll(transport)
379+ updatedPendingQueries <- pendingQueries.resendAll(transport)
350380 } yield Connected (
351381 config = config,
352382 sessionId = sessionId,
@@ -355,6 +385,7 @@ object RaftClient {
355385 serverRequestTracker = serverRequestTracker,
356386 nextRequestId = nextRequestId,
357387 pendingRequests = updatedPending,
388+ pendingQueries = updatedPendingQueries,
358389 currentMemberId = currentMemberId
359390 )
360391 } else {
@@ -387,6 +418,14 @@ object RaftClient {
387418 newPending = pendingRequests.add(requestId, payload, promise, now)
388419 } yield copy(pendingRequests = newPending)
389420
421+ case StreamEvent .Action (ClientAction .SubmitQuery (payload, promise)) =>
422+ // Queue query while connecting (handled after session established)
423+ for {
424+ now <- Clock .instant
425+ correlationId <- Random .nextUUID.map(u => CorrelationId .fromString(u.toString))
426+ newPending = pendingQueries.add(correlationId, payload, promise, now)
427+ } yield copy(pendingQueries = newPending)
428+
390429 case StreamEvent .TimeoutCheck =>
391430 for {
392431 now <- Clock .instant
@@ -405,6 +444,9 @@ object RaftClient {
405444 case StreamEvent .ServerMsg (ClientResponse (_, _)) =>
406445 ZIO .logWarning(" Received ClientResponse while connecting, ignoring" ).as(this )
407446
447+ case StreamEvent .ServerMsg (QueryResponse (correlationId, result)) =>
448+ ZIO .logWarning(" Received QueryResponse while connecting existing session, ignoring" ).as(this )
449+
408450 case StreamEvent .ServerMsg (KeepAliveResponse (_)) =>
409451 ZIO .succeed(this )
410452
@@ -439,6 +481,7 @@ object RaftClient {
439481 serverRequestTracker : ServerRequestTracker ,
440482 nextRequestId : RequestIdRef ,
441483 pendingRequests : PendingRequests ,
484+ pendingQueries : PendingQueries ,
442485 currentMemberId : MemberId
443486 ) extends ClientState {
444487 override def stateName : String = s " Connected( $sessionId) "
@@ -472,7 +515,8 @@ object RaftClient {
472515 createdAt = now,
473516 serverRequestTracker = serverRequestTracker,
474517 nextRequestId = nextRequestId,
475- pendingRequests = pendingRequests
518+ pendingRequests = pendingRequests,
519+ pendingQueries = pendingQueries
476520 )
477521 }
478522
@@ -501,9 +545,19 @@ object RaftClient {
501545 _ <- transport.sendMessage(request).orDie
502546 newPending = pendingRequests.add(requestId, payload, promise, now)
503547 } yield copy(pendingRequests = newPending)
548+ case StreamEvent .Action (ClientAction .SubmitQuery (payload, promise)) =>
549+ for {
550+ now <- Clock .instant
551+ // correlationId via client-side generator (to be implemented in T024)
552+ correlationId <- Random .nextUUID.map(u => CorrelationId .fromString(u.toString))
553+ newPending = pendingQueries.add(correlationId, payload, promise, now)
554+ _ <- transport.sendMessage(Query (correlationId, payload, now)).orDie
555+ } yield copy(pendingQueries = newPending)
504556
505557 case StreamEvent .ServerMsg (ClientResponse (requestId, result)) =>
506558 pendingRequests.complete(requestId, result).map(newPending => copy(pendingRequests = newPending))
559+ case StreamEvent .ServerMsg (QueryResponse (correlationId, result)) =>
560+ pendingQueries.complete(correlationId, result).map(newPending => copy(pendingQueries = newPending))
507561
508562 case StreamEvent .ServerMsg (RequestError (requestId, RequestErrorReason .ResponseEvicted )) =>
509563 if (pendingRequests.contains(requestId))
@@ -581,8 +635,9 @@ object RaftClient {
581635 case StreamEvent .TimeoutCheck =>
582636 for {
583637 now <- Clock .instant
584- newPending <- pendingRequests.resendExpired(transport, now, config.requestTimeout)
585- } yield copy(pendingRequests = newPending)
638+ newPendingReqs <- pendingRequests.resendExpired(transport, now, config.requestTimeout)
639+ newPendingQs <- pendingQueries.resendExpired(transport, now, config.requestTimeout)
640+ } yield copy(pendingRequests = newPendingReqs, pendingQueries = newPendingQs)
586641
587642 case StreamEvent .ServerMsg (SessionCreated (_, _)) =>
588643 ZIO .logWarning(" Received SessionCreated while connected, ignoring" ).as(this )
0 commit comments