Skip to content

Commit 186e5f8

Browse files
authored
Randomize CorrelationId generation per host (#9213)
* Randomize CorrelationId generation per host * Add JobAttempt to pipeline artifact
1 parent 909769c commit 186e5f8

File tree

2 files changed

+51
-19
lines changed

2 files changed

+51
-19
lines changed

src/Orleans.Core/Messaging/CorrelationId.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Runtime.CompilerServices;
23

34
#nullable enable
45
namespace Orleans.Runtime
@@ -28,13 +29,21 @@ namespace Orleans.Runtime
2829

2930
public int CompareTo(CorrelationId other) => id.CompareTo(other.id);
3031

31-
public override string ToString() => id.ToString();
32+
public override string ToString() => id.ToString("X16");
3233

33-
string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => id.ToString(format, formatProvider);
34+
string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => id.ToString(format ?? "X16", formatProvider);
3435

3536
bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
36-
=> id.TryFormat(destination, out charsWritten, format, provider);
37+
{
38+
if (format.IsEmpty)
39+
{
40+
format = "X16";
41+
}
3742

43+
return id.TryFormat(destination, out charsWritten, format, provider);
44+
}
45+
46+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3847
internal long ToInt64() => id;
3948
}
4049
}

src/Orleans.Core/Messaging/MessageFactory.cs

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
using System;
33
using System.Collections.Generic;
4+
using System.IO.Hashing;
5+
using System.Numerics;
46
using Microsoft.Extensions.Logging;
57
using Orleans.CodeGeneration;
68
using Orleans.Serialization;
@@ -9,34 +11,55 @@ namespace Orleans.Runtime
911
{
1012
internal class MessageFactory
1113
{
12-
private readonly DeepCopier deepCopier;
13-
private readonly ILogger logger;
14-
private readonly MessagingTrace messagingTrace;
14+
[ThreadStatic]
15+
private static ulong _nextId;
16+
17+
// The nonce reduces the chance of an id collision for a given grain to effectively zero. Id collisions are only relevant in scenarios
18+
// where where the infinitesimally small chance of a collision is acceptable, such as call cancellation.
19+
private readonly ulong _seed;
20+
private readonly DeepCopier _deepCopier;
21+
private readonly ILogger _logger;
22+
private readonly MessagingTrace _messagingTrace;
1523

1624
public MessageFactory(DeepCopier deepCopier, ILogger<MessageFactory> logger, MessagingTrace messagingTrace)
1725
{
18-
this.deepCopier = deepCopier;
19-
this.logger = logger;
20-
this.messagingTrace = messagingTrace;
26+
_deepCopier = deepCopier;
27+
_logger = logger;
28+
_messagingTrace = messagingTrace;
29+
30+
// Generate a 64-bit nonce for the host, to be combined with per-message correlation ids to get a unique, per-host value.
31+
// This avoids id collisions across different hosts for a given grain.
32+
_seed = unchecked((ulong)Random.Shared.NextInt64());
2133
}
2234

2335
public Message CreateMessage(object body, InvokeMethodOptions options)
2436
{
2537
var message = new Message
2638
{
2739
Direction = (options & InvokeMethodOptions.OneWay) != 0 ? Message.Directions.OneWay : Message.Directions.Request,
28-
Id = CorrelationId.GetNext(),
40+
Id = GetNextCorrelationId(),
2941
IsReadOnly = (options & InvokeMethodOptions.ReadOnly) != 0,
3042
IsUnordered = (options & InvokeMethodOptions.Unordered) != 0,
3143
IsAlwaysInterleave = (options & InvokeMethodOptions.AlwaysInterleave) != 0,
3244
BodyObject = body,
33-
RequestContextData = RequestContextExtensions.Export(this.deepCopier),
45+
RequestContextData = RequestContextExtensions.Export(_deepCopier),
3446
};
3547

36-
messagingTrace.OnCreateMessage(message);
48+
_messagingTrace.OnCreateMessage(message);
3749
return message;
3850
}
3951

52+
private CorrelationId GetNextCorrelationId()
53+
{
54+
// To avoid cross-thread coordination, combine a thread-local counter with the managed thread id. The values are XOR'd together with a
55+
// 64-bit nonce. Rotating the thread id reduces the chance of collision further by putting the significant bits at the high end, where
56+
// they are less likely to collide with the per-thread counter, which could become relevant if the counter exceeded 2^32.
57+
var managedThreadId = Environment.CurrentManagedThreadId;
58+
var tid = (ulong)(managedThreadId << 16 | managedThreadId >> 16) << 32;
59+
var id = _seed ^ tid ^ ++_nextId;
60+
return new CorrelationId(unchecked((long)id));
61+
}
62+
4063
public Message CreateResponseMessage(Message request)
4164
{
4265
var response = new Message
@@ -52,25 +75,25 @@ public Message CreateResponseMessage(Message request)
5275
SendingGrain = request.TargetGrain,
5376
CacheInvalidationHeader = request.CacheInvalidationHeader,
5477
TimeToLive = request.TimeToLive,
55-
RequestContextData = RequestContextExtensions.Export(this.deepCopier),
78+
RequestContextData = RequestContextExtensions.Export(_deepCopier),
5679
};
5780

58-
messagingTrace.OnCreateMessage(response);
81+
_messagingTrace.OnCreateMessage(response);
5982
return response;
6083
}
6184

6285
public Message CreateRejectionResponse(Message request, Message.RejectionTypes type, string info, Exception ex = null)
6386
{
64-
var response = this.CreateResponseMessage(request);
87+
var response = CreateResponseMessage(request);
6588
response.Result = Message.ResponseTypes.Rejection;
6689
response.BodyObject = new RejectionResponse
6790
{
6891
RejectionType = type,
6992
RejectionInfo = info,
7093
Exception = ex,
7194
};
72-
if (this.logger.IsEnabled(LogLevel.Debug))
73-
this.logger.LogDebug(
95+
if (_logger.IsEnabled(LogLevel.Debug))
96+
_logger.LogDebug(
7497
ex,
7598
"Creating {RejectionType} rejection with info '{Info}' at:" + Environment.NewLine + "{StackTrace}",
7699
type,
@@ -81,11 +104,11 @@ public Message CreateRejectionResponse(Message request, Message.RejectionTypes t
81104

82105
internal Message CreateDiagnosticResponseMessage(Message request, bool isExecuting, bool isWaiting, List<string> diagnostics)
83106
{
84-
var response = this.CreateResponseMessage(request);
107+
var response = CreateResponseMessage(request);
85108
response.Result = Message.ResponseTypes.Status;
86109
response.BodyObject = new StatusResponse(isExecuting, isWaiting, diagnostics);
87110

88-
if (this.logger.IsEnabled(LogLevel.Debug)) this.logger.LogDebug("Creating {RequestMessage} status update with diagnostics {Diagnostics}", request, diagnostics);
111+
if (_logger.IsEnabled(LogLevel.Debug)) _logger.LogDebug("Creating {RequestMessage} status update with diagnostics {Diagnostics}", request, diagnostics);
89112

90113
return response;
91114
}

0 commit comments

Comments
 (0)