hubcio commented on code in PR #4506:
URL: https://github.com/apache/iggy/pull/4506#discussion_r4270145159
##########
core/server/src/dispatch/mod.rs:
##########
@@ -256,25 +440,50 @@ async fn drain_client_requests<B, MJ, S, SB>(
SB: SuperblockStore + 'static,
{
loop {
- let Some(message) = pop_next_client_request(&queues, client_id) else {
+ let metadata_frontier =
shard.plane.metadata().applied_frontier().get();
+ let next = pop_next_client_request(&queues, client_id, |queued| {
+ if is_ping(&queued.message) {
+ return true;
+ }
+ if drain.metadata_write.get() {
+ return false;
+ }
+ queued
+ .progress_key(metadata_frontier, |message|
progress_key(&shard, message))
+ .is_none_or(|key| {
+ drain.in_flight.get() < drain.capacity
+ && !drain.progress.borrow().contains(&key)
Review Comment:
PR4 now tests a held keyed Store followed by queued Next(auto=false) and
GetConsumerOffset: both remain behind its progress guard. Optional shared
read-only lanes are deferred to PR6 and require explicit Store/Delete ordering
regressions before changing that serialization. This split is recorded in B7
and the PR6 plan.
##########
foreign/go/client/tcp/tcp_core.go:
##########
@@ -860,6 +857,9 @@ func (c *IggyTcpClient) attempt(
if captured, ok :=
ctx.Value(capturedPartitionContext{}).(iggcon.PartitionContext); ok {
Review Comment:
PR4 removes the duplicate context lookup. The remaining context.WithValue
boxing/allocation work is deferred to PR6, recorded with Go RTT, copies, key
reuse and locking measurements in B7. Those measurements follow PR5's managed
link/heartbeat lifecycle gates; they do not replace the lifecycle proof.
##########
foreign/csharp/Iggy_SDK/IggyClient/Implementations/TcpMessageStream.PollRouting.cs:
##########
@@ -201,105 +120,142 @@ private static IggyInvalidStatusCodeException
TruncatedSend()
return VsrError.Exception(VsrError.INVALID_COMMAND, "The SendMessages
body is truncated.");
}
- private static ContextQuery? TryBuildContextQuery(int code,
ReadOnlySpan<byte> body)
+ private static SendTarget PartitionTarget(int code, ReadOnlySpan<byte>
body)
{
- if (code is CommandCodes.STORE_CONSUMER_OFFSET_CODE or
CommandCodes.DELETE_CONSUMER_OFFSET_CODE)
+ if (code == CommandCodes.SEND_MESSAGES_CODE)
{
- var suffixSize = code == CommandCodes.STORE_CONSUMER_OFFSET_CODE
- ? StoreOffsetSuffixSize : DeleteOffsetSuffixSize;
- if (body.Length <= suffixSize)
- {
- return null;
- }
- var query = body[..^suffixSize].ToArray();
- return new
ContextQuery(CommandCodes.GET_CONSUMER_OFFSET_ROUTING_CODE, query,
ConsumerSessionSize,
- Convert.ToBase64String(query));
+ return ParseSendTarget(body);
}
- if (code != CommandCodes.POLL_MESSAGES_CODE || body.Length <=
PollOptionsSize)
+
+ if (body.IsEmpty)
{
- return null;
+ throw TruncatedSend();
}
- return new ContextQuery(CommandCodes.GET_POLL_ROUTING_CODE,
body.ToArray(), ConsumerSessionSize,
- Convert.ToBase64String(body[..^PollOptionsSize]));
+ var group = body[0] == ConsumerGroupWireKind;
+ var position = 1;
+ _ = ReadIdentifier(body, ref position);
+ var identifiersStart = position;
+ var streamId = ReadIdentifier(body, ref position);
+ var topicId = ReadIdentifier(body, ref position);
+ var identifiersLength = position - identifiersStart;
+ if (position + 1 + sizeof(uint) > body.Length)
+ {
+ throw TruncatedSend();
+ }
+ var present = body[position++] != 0;
+ if (!present && (code is CommandCodes.STORE_CONSUMER_OFFSET_CODE or
CommandCodes.DELETE_CONSUMER_OFFSET_CODE
+ || code == CommandCodes.POLL_MESSAGES_CODE && group))
+ {
+ throw VsrError.Exception(VsrError.INVALID_COMMAND, "This partition
request requires an explicit partition id.");
+ }
+ var partitionId = present ?
BinaryPrimitives.ReadUInt32LittleEndian(body[position..]) : 0;
+ var query = new byte[identifiersLength + sizeof(uint)];
+ body.Slice(identifiersStart, identifiersLength).CopyTo(query);
+
BinaryPrimitives.WriteUInt32LittleEndian(query.AsSpan(identifiersLength),
partitionId);
+ return new SendTarget(new PartitionRouteKey(streamId, topicId,
partitionId), query);
+ }
+
+ private static byte[] RouteQuery(PartitionRouteKey key)
+ {
+ var query = new byte[IdentifierPrefixSize * 2 + key.StreamId.Length +
key.TopicId.Length + sizeof(uint)];
+ query.AsSpan().WriteBytesFromStreamAndTopicIdentifiers(key.StreamId,
key.TopicId);
+ BinaryPrimitives.WriteUInt32LittleEndian(query.AsSpan(query.Length -
sizeof(uint)), key.PartitionId);
+ return query;
}
- /// <summary>
- /// Sends a poll or an offset write to the partition primary, over a
data connection that leaves the
- /// membership connection where it is. A caller context, or else the
first context a route reports, is
- /// retained across every retry, so a retry never moves the request
onto a newer incarnation or owner.
- /// </summary>
- /// <param name="code">The poll or offset write command code.</param>
- /// <param name="key">The cached route of the request.</param>
- /// <param name="routing">The body of the routing request.</param>
- /// <param name="payload">The body of the request.</param>
- /// <param name="context">The caller context, or null to take the context
the route reports.</param>
- /// <param name="token">The cancellation token.</param>
- private async Task<IMemoryOwner<byte>> SendRoutedAsync(int code,
PollRouteKey key, ReadOnlyMemory<byte> routing,
+ private async Task<IMemoryOwner<byte>> SendRoutedAsync(int code,
SendTarget target,
ReadOnlyMemory<byte> payload, PartitionContext? context,
CancellationToken token)
{
using var cancellation =
CancellationTokenSource.CreateLinkedTokenSource(token);
cancellation.CancelAfter(VsrRequestTimeoutMs);
var deadline = Environment.TickCount64 + VsrRequestTimeoutMs;
+ // A new route can change the destination, but not the request's
identity or captured position.
var captured = context;
+ SessionFrame? frame = null;
+ ulong? generation = null;
+ var refreshHistory = code == CommandCodes.SEND_MESSAGES_CODE &&
context is null;
+ var refusedPrimary = false;
+ var lifecycleInterval = LifecycleRetryIntervalMs;
try
{
while (true)
{
try
{
- if (Volatile.Read(ref _clusterNodeCount) == 0)
+ var route = await GetPartitionRouteAsync(target.Key,
target.Query, deadline, cancellation.Token);
+ captured ??= route.Route.Context;
+ if (generation is not null && generation !=
route.Generation)
{
- using var metadata = await
SendPollControlAsync(CommandCodes.GET_CLUSTER_METADATA_CODE,
- ReadOnlyMemory<byte>.Empty, deadline,
cancellation.Token);
- if (metadata.Memory.Length == 0)
- {
- throw new MalformedResponseException("Poll routing
requires a nonempty cluster roster.");
- }
-
-
RememberRoster(BinaryMapper.MapClusterMetadata(metadata.Memory.Span));
+ throw PollNotAccepted();
}
-
+ generation ??= route.Generation;
+ frame ??=
_consensusSession.Resolve(VsrOperations.ForCode(code));
if (Volatile.Read(ref _clusterNodeCount) <= 1)
{
- captured ??= await CapturePartitionContextAsync(code,
payload, cancellation.Token);
- return await SendWithResponseAsync(code, payload,
token: cancellation.Token,
- capturedContext: captured);
+ var attempt = await SendVsrAttemptAsync(code, payload,
Environment.TickCount64, deadline,
+ false, cancellation.Token, captured.Value, frame,
+ retryTransient: VsrOperations.ForCode(code) !=
VsrOperation.NonReplicated);
+ if (attempt.Error is not null)
+ {
+ if (attempt.RequestStarted &&
!VsrOperations.IsReplaySafeRead(code, false, payload.Span)
+ && !IsDefinitiveVerdict(attempt.Error))
+ {
+ throw new
VsrRequestOutcomeUnknownException(attempt.Error);
+ }
+ ExceptionDispatchInfo.Throw(attempt.Error);
+ }
+ return attempt.Response!;
}
- var route = await GetPollRouteAsync(key, routing,
deadline, cancellation.Token);
- captured ??= route.Context;
- return await SendPrimaryOnceAsync(key,
- code == CommandCodes.POLL_MESSAGES_CODE ?
CommandCodes.POLL_MESSAGES_ON_PRIMARY_CODE : code,
- payload, route, captured.Value, deadline,
cancellation.Token);
+ return await SendPrimaryOnceAsync(target.Key, code,
payload, route, captured.Value,
+ frame.Value, deadline, cancellation.Token);
}
- catch (IggyInvalidStatusCodeException error) when
(error.StatusCode == VsrError.TRANSIENT_NOT_ACCEPTED)
+ catch (IggyInvalidStatusCodeException error) when
(error.StatusCode is
+ VsrError.TRANSIENT_NOT_ACCEPTED or VsrError.NOT_PRIMARY)
{
- RemovePollRoute(key);
- if (Environment.TickCount64 + PollRoutingRetryIntervalMs
>= deadline)
+ RemovePartitionRoute(target.Key);
+ if ((generation is not null && generation !=
_consensusSession.Generation)
Review Comment:
Deferred to PR5 native route-activation acceptance. B7 and the PR5 plan
require LifecycleBusy or HistoryUnavailable followed by relogin so the
post-refresh generation guard is exercised independently. Removing either guard
alone must fail its own regression while the other remains enabled. The
existing dual-guard mutation test is not claimed as independent proof.
##########
core/shard/src/lib.rs:
##########
@@ -5197,7 +5345,18 @@ where
let Some(partition) = planes.1.0.get_mut_by_ns(&namespace) else {
return;
};
- if !partition.consensus().is_normal() {
+ if !self.partition_incarnation_matches(
+ partition,
+ header.partition_incarnation,
+ Command::RequestPrepares,
+ ) {
+ return;
+ }
+ if !matches!(
Review Comment:
The new ViewChange donor exposure is fixed in b048d0263: requests carry the
requester view; a ViewChange donor serves its uncommitted suffix only to that
view's primary, while ordinary backups and other views receive at most its
locally applied prefix. RepairDone/RangeEvicted must also match the selected
peer and nonce. The regression captures real repair frames and preserves
primary-elect missing-body serving.
The broader pre-existing requester-side trust problem is deferred to PR6 as
a correctness/release gate in B5 and the plan. Repair bodies still lack
sender/nonce attribution. Closure requires canonical or committed evidence for
unanchored history and end-to-end five-/seven-replica commit, failover and
readback tests, including stale normal donors, blank canonical heads and
delayed bodies after peer rotation. The donor regression and terminator check
do not claim that broader proof.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]