feat(grpc-events): handle-capture cycle — event-row gap proven NOT a client payload issue
Extends the instrument-grpc rewrite to log string (strHandle) + uint (uiHandle / queryRequestType) params, not just byte[], and captures our SDK's live v8 openParameters for a byte-diff against the native. Result of the exhaustive comparison (all live-confirmed via the opt-in EventReadDiagnostic test): - StartEventQuery request: byte-identical to the native (v6 layout) - v8 OpenConnection openParameters: byte-identical to the native (302B) once ClientNodeName matches — every control byte/ConnectionType/token/ShardId - handle usage identical: ExchangeKey->contextKey, registration->storage GUID (strHandle), query->client uint (uiHandle); handles valid (RTag/EnsT=True) - queryRequestType=3, registration order, gzip metadata header — all match - window has events (native returns 50 now); eventCount not it Every observable client-side byte matches the native, yet the server scopes 0 events to our connection. The event RPCs succeed over our transport and return a valid EMPTY result (not a transport error), so this is a connection/server-level difference (session affinity tied to the native Grpc.Core HTTP/2 connection or a connection identity used to scope events) — invisible to and unfixable by client payload matching. Needs server-side insight, not more wire RE. Added opt-in diagnostics (RegistrationDiag, LastResultBufferHex, LastEventOpenRequestHex). 326/326 offline; gated test still pins the no-row throw. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
This commit is contained in:
@@ -1387,10 +1387,14 @@ static int InstrumentGrpcNonStream(string[] args)
|
||||
|
||||
ModuleDefMD module = ModuleDefMD.Load(sourcePath);
|
||||
MemberRefUser logByteArray = CreateLogByteArrayRef(module);
|
||||
MemberRefUser logString = CreateLogStringRef(module);
|
||||
MemberRefUser logUInt32 = CreateLogUInt32Ref(module);
|
||||
|
||||
// Cast a wide net: instrument EVERY byte[]-input method on every Grpc*Client type, so whichever
|
||||
// path the native non-streamed write actually drives (History/Transaction RegisterTags +
|
||||
// AddNonStreamValues, or a Storage-service route) is captured. Phase = "<Type>.<Method>.<param>".
|
||||
// Also captures string (strHandle) and uint (uiHandle / queryRequestType) inputs so the event-read
|
||||
// handle fields are visible, not just byte[] params.
|
||||
var instrumented = new List<object>();
|
||||
foreach (TypeDef type in module.GetTypes()
|
||||
.Where(t => t.Name.String.StartsWith("Grpc", StringComparison.Ordinal) && t.Name.String.EndsWith("Client", StringComparison.Ordinal)))
|
||||
@@ -1431,6 +1435,34 @@ static int InstrumentGrpcNonStream(string[] args)
|
||||
});
|
||||
}
|
||||
|
||||
// ENTRY: log string (strHandle) and uint (uiHandle / queryRequestType / count) inputs.
|
||||
foreach (dnlib.DotNet.Parameter scalarParam in method.Parameters
|
||||
.Where(p => !p.IsHiddenThisParameter && (p.Type.FullName == "System.String" || p.Type.FullName == "System.UInt32"))
|
||||
.ToArray())
|
||||
{
|
||||
bool isString = scalarParam.Type.FullName == "System.String";
|
||||
string phase = $"{type.Name}.{method.Name}.{scalarParam.Name}.{(isString ? "str" : "u32")}";
|
||||
Instruction[] injected =
|
||||
[
|
||||
Instruction.Create(OpCodes.Ldstr, phase),
|
||||
Instruction.Create(OpCodes.Ldarg, scalarParam),
|
||||
Instruction.Create(OpCodes.Call, isString ? logString : logUInt32),
|
||||
];
|
||||
foreach (Instruction instruction in injected.Reverse())
|
||||
{
|
||||
method.Body.Instructions.Insert(0, instruction);
|
||||
}
|
||||
method.Body.MaxStack = (ushort)Math.Max((int)method.Body.MaxStack, 8);
|
||||
instrumented.Add(new
|
||||
{
|
||||
Type = type.Name.String,
|
||||
Method = method.Name.String,
|
||||
Phase = phase,
|
||||
Direction = "in",
|
||||
Token = "0x" + method.MDToken.Raw.ToString("X8"),
|
||||
});
|
||||
}
|
||||
|
||||
// EXIT: log out/ref byte[] responses ("System.Byte[]&") before each ret. ldarg loads the
|
||||
// managed pointer; ldind.ref dereferences it to the byte[]. (RPC wrappers set the out
|
||||
// param right before a single ret, so branch-to-ret skew is not a concern here.)
|
||||
|
||||
Reference in New Issue
Block a user