-
Notifications
You must be signed in to change notification settings - Fork 1.1k
[WIP] Akka.IO / Akka.Streams: ByteString rewrite
#7136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 3 commits
96a6d11
486f5a3
d9c3d98
cd47df3
31122d7
8cbb00b
53a7224
1e37898
0726e2d
abc6aa4
aca4c72
ded7e5c
1a93896
27ae16d
79b01bd
f63896f
4b69e69
411dcb5
8a25c7a
e73aef8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -169,39 +169,45 @@ public FramingException(string message) : base(message) | |
| protected FramingException(SerializationInfo info, StreamingContext context) : base(info, context) { } | ||
| } | ||
|
|
||
| private static readonly Func<IEnumerator<byte>, int, int> BigEndianDecoder = (enumerator, length) => | ||
| private static int BigEndianDecoder(ref ReadOnlySpan<byte> enumerator, int length) | ||
| { | ||
| var count = length; | ||
| var decoded = 0; | ||
| while (count > 0) | ||
| if (length > enumerator.Length) | ||
| { | ||
| throw new ArgumentException("Length exceeds the size of the enumerator."); | ||
| } | ||
|
|
||
| var result = 0; | ||
|
|
||
| // Assuming 'length' is 4 for a 32-bit integer. | ||
| // Adjust the loop and shift amounts if dealing with different sizes. | ||
| for (var i = 0; i < length; i++) | ||
| { | ||
| decoded <<= 8; | ||
| if (!enumerator.MoveNext()) throw new IndexOutOfRangeException("LittleEndianDecoder reached end of byte string"); | ||
| decoded |= enumerator.Current & 0xFF; | ||
| count--; | ||
| result |= enumerator[i] << ((length - 1 - i) * 8); | ||
| } | ||
|
|
||
| return decoded; | ||
| }; | ||
| return result; | ||
| } | ||
|
|
||
| private static readonly Func<IEnumerator<byte>, int, int> LittleEndianDecoder = (enumerator, length) => | ||
| private static int LittleEndianDecoder(ref ReadOnlySpan<byte> span, int length) | ||
| { | ||
| var highestOcted = (length - 1) << 3; | ||
| var mask = (int) (1L << (length << 3)) - 1; | ||
| var count = length; | ||
| if (length > span.Length) | ||
| { | ||
| throw new IndexOutOfRangeException("LittleEndianDecoder reached end of byte span"); | ||
| } | ||
|
|
||
| var decoded = 0; | ||
| var highestOctetShift = (length - 1) << 3; | ||
| var mask = (int)(1L << (length << 3)) - 1; | ||
|
|
||
| while (count > 0) | ||
| for (var i = 0; i < length; i++) | ||
| { | ||
| // decoded >>>= 8 on the jvm | ||
| decoded = (int) ((uint) decoded >> 8); | ||
| if (!enumerator.MoveNext()) throw new IndexOutOfRangeException("LittleEndianDecoder reached end of byte string"); | ||
| decoded += (enumerator.Current & 0xFF) << highestOcted; | ||
| count--; | ||
| // Shift and add the ith byte to 'decoded'. No need for >>>= as in JVM; just shift appropriately. | ||
| var shiftAmount = highestOctetShift - (i << 3); | ||
| decoded |= (span[i] & 0xFF) << shiftAmount; | ||
| } | ||
|
|
||
| return decoded & mask; | ||
| }; | ||
| } | ||
|
|
||
| private sealed class SimpleFramingProtocolEncoderStage : SimpleLinearGraphStage<ByteString> | ||
| { | ||
|
|
@@ -342,8 +348,8 @@ private void DoParse() | |
| else if (_buffer.HasSubstring(_stage._separatorBytes, possibleMatchPosition)) | ||
| { | ||
| // Found a match | ||
| var parsedFrame = _buffer.Slice(0, possibleMatchPosition).Compact(); | ||
| _buffer = _buffer.Slice(possibleMatchPosition + _stage._separatorBytes.Count).Compact(); | ||
| var parsedFrame = _buffer.Slice(0, possibleMatchPosition); | ||
| _buffer = _buffer.Slice(possibleMatchPosition + _stage._separatorBytes.Count); | ||
| _nextPossibleMatch = 0; | ||
| Push(_stage.Outlet, parsedFrame); | ||
|
|
||
|
|
@@ -422,7 +428,7 @@ public override void OnUpstreamFinish() | |
| /// </summary> | ||
| private void PushFrame() | ||
| { | ||
| var emit = _buffer.Slice(0, _frameSize).Compact(); | ||
| var emit = _buffer.Slice(0, _frameSize); | ||
| _buffer = _buffer.Slice(_frameSize); | ||
| _frameSize = int.MaxValue; | ||
| Push(_stage.Outlet, emit); | ||
|
|
@@ -440,9 +446,14 @@ private void TryPushFrame() | |
| PushFrame(); | ||
| else if (bufferSize >= _stage._minimumChunkSize) | ||
| { | ||
| var iterator = _buffer.Slice(_stage._lengthFieldOffset).GetEnumerator(); | ||
| var parsedLength = _stage._intDecoder(iterator, _stage._lengthFieldLength); | ||
|
|
||
| var iterator = _buffer.Memory.Span.Slice(_stage._lengthFieldOffset); | ||
| var parsedLength = _stage._byteOrder switch { | ||
| ByteOrder.BigEndian => BigEndianDecoder(ref iterator, _stage._lengthFieldLength), | ||
| ByteOrder.LittleEndian => LittleEndianDecoder(ref iterator, _stage._lengthFieldLength), | ||
| _ => throw new NotSupportedException($"ByteOrder {_stage._byteOrder} is not supported") | ||
| }; | ||
|
|
||
| // TODO: AVOID ARRAY COPYING AGAIN HERE | ||
|
||
| _frameSize = _stage._computeFrameSize.HasValue | ||
| ? _stage._computeFrameSize.Value(_buffer.Slice(0, _stage._lengthFieldOffset).ToArray(), parsedLength) | ||
| : parsedLength + _stage._minimumChunkSize; | ||
|
|
@@ -480,7 +491,7 @@ private void TryPull() | |
| private readonly int _maximumFramelength; | ||
| private readonly int _lengthFieldOffset; | ||
| private readonly int _minimumChunkSize; | ||
| private readonly Func<IEnumerator<byte>, int, int> _intDecoder; | ||
| private readonly ByteOrder _byteOrder; | ||
| private readonly Option<Func<IReadOnlyList<byte>, int, int>> _computeFrameSize; | ||
|
|
||
| // For the sake of binary compatibility | ||
|
|
@@ -500,7 +511,7 @@ public LengthFieldFramingStage( | |
| _lengthFieldOffset = lengthFieldOffset; | ||
| _minimumChunkSize = lengthFieldOffset + lengthFieldLength; | ||
| _computeFrameSize = computeFrameSize; | ||
| _intDecoder = byteOrder == ByteOrder.BigEndian ? BigEndianDecoder : LittleEndianDecoder; | ||
| _byteOrder = byteOrder; | ||
| } | ||
|
|
||
| protected override GraphStageLogic CreateLogic(Attributes inheritedAttributes) => new Logic(this); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,44 +82,13 @@ public void Release(SocketAsyncEventArgs e) | |
|
|
||
| internal static class SocketAsyncEventArgsExtensions | ||
| { | ||
| public static void SetBuffer(this SocketAsyncEventArgs args, ByteString data) | ||
| { | ||
| if (data.IsCompact) | ||
| { | ||
| var buffer = data.Buffers[0]; | ||
| if (args.BufferList != null) | ||
| { | ||
| // BufferList property setter is not simple member association operation, | ||
| // but the getter is. Therefore we first check if we need to clear buffer list | ||
| // and only do so if necessary. | ||
| args.BufferList = null; | ||
| } | ||
| args.SetBuffer(buffer.Array, buffer.Offset, buffer.Count); | ||
| } | ||
| else | ||
| { | ||
| if (RuntimeDetector.IsMono) | ||
| { | ||
| // Mono doesn't support BufferList - falback to compacting ByteString | ||
| var compacted = data.Compact(); | ||
| var buffer = compacted.Buffers[0]; | ||
| args.SetBuffer(buffer.Array, buffer.Offset, buffer.Count); | ||
| } | ||
| else | ||
| { | ||
| args.SetBuffer(null, 0, 0); | ||
| args.BufferList = data.Buffers; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public static void SetBuffer(this SocketAsyncEventArgs args, IEnumerable<ByteString> dataCollection) | ||
| { | ||
| if (RuntimeDetector.IsMono) | ||
| { | ||
| // Mono doesn't support BufferList - falback to compacting ByteString | ||
| var dataList = dataCollection.ToList(); | ||
| var totalSize = dataList.SelectMany(d => d.Buffers).Sum(d => d.Count); | ||
| var totalSize = dataList.Count; | ||
| var bytes = new byte[totalSize]; | ||
| var position = 0; | ||
| foreach (var byteString in dataList) | ||
|
|
@@ -132,7 +101,8 @@ public static void SetBuffer(this SocketAsyncEventArgs args, IEnumerable<ByteStr | |
| else | ||
| { | ||
| args.SetBuffer(null, 0, 0); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Food for thought:
|
||
| args.BufferList = dataCollection.SelectMany(d => d.Buffers).ToList(); | ||
| // TODO: fix this before we ship | ||
| args.BufferList = new List<ArraySegment<byte>>(dataCollection.Select(bs => new ArraySegment<byte>(bs.ToArray()))); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thing that feels very relevant, Short term, This -screams- pooling scenario and/or I remember hacking something together with one pool or another during my Covid lockdown 'Akka streams remote transport experiment' and it helped a LOT. I think it was somewhere during or just before the hand written protobuf stuff. Where 'considerations' get important, is the pinning of the arrays underlying the bufferlist. IDK the current pinning 'status' but last I dug into they all get pinned for a while, and that sucks for the GC. Thought/suggestion would be in general to use a pooled buffer of some form, but also strongly to consider/measure a single final buffer per Having a pooled buffer increases likelyhood that it will, long term, survive to an older segment and hopefully avoid pinning costs in Gen0/Gen1. (Also, longer term, we can hopefully There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than go down that road, I'd prefer to rewrite Akka.IO to not depend on |
||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -945,6 +945,7 @@ public override void DoWrite(ConnectionInfo info) | |
| { | ||
| try | ||
| { | ||
| // TODO: avoid use of SocketAsyncEventArgs on newer platforms | ||
|
||
| _sendArgs.SetBuffer(_dataToSend); | ||
| if (!_connection.Socket.SendAsync(_sendArgs)) | ||
| _self.Tell(SocketSent.Instance); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.