Repository navigation
SharedMemory v2.3.4-rc.1
Pre-releaseRelease candidate for v2.3.4.
Bug fixes. No public members are added, removed or changed, and the target frameworks and dependencies are the same as 2.3.3.
Upgrading from 2.3.3
2.3.4 changes the size or layout of some element types in shared memory, and gives most types one layout on every runtime (#81). Depending on the type and the runtime, a 2.3.3 process reads and writes them at different offsets. If processes share a buffer of these types, upgrade them all together:
bool,char, and structures that hold them;- structures that hold a
DateTimeor adecimal; - a few rarer cases, listed in the README.
The README's element sizes section says which types change on which runtimes, what FastStructure<T>.Size means for them, and how to keep sharing bool and char data with 2.3.3.
2.3.3 and 2.3.4 processes open each other's buffers, and their RpcBuffers talk to each other. The #78 fix is on the receiving side only, so a 2.3.3 process still zeroes the last packet of a message whose length is a multiple of the packet size. #87's Compatibility section lists every other difference a 2.3.3 process can see.
Fixed
CircularBuffer
- A buffer opened while its owner was still creating it could fail its first
Write, which returned 0 after the full timeout (#77).
RpcBuffer
- A request or response whose length was a multiple of the packet size,
bufferCapacity - 32bytes, arrived with its last packet zeroed (#78). - A send that found no room for a packet in time dropped the packet and still reported success (#52).
- Code after
await RemoteRequestAsync(...)could run on the RPC read thread, where a blockingRemoteRequestmade from that code timed out and slow code stalled every response (#52). - One malformed packet could stop the channel reading for good, so every later request timed out (#52).
- Timed-out and cancelled requests stayed in memory, and
RpcStatisticsmiscounted (#52). - A send waiting for room kept waiting for up to 1 s after the owner closed the channel, holding up other sends. It now stops within about 50 ms (#52).
- A restarted client could receive the late reply meant for its previous instance (#79).
- A constructor that failed part-way, for example because another process still held the channel's name, left the name unusable until a garbage collection (#86).
SharedArray, FastStructure and array copies
- Arrays of
bool,charor structures holding them were copied with the wrong number of bytes.boolarrays could crash the process,chararrays came back half copied, andSharedArray<char>elements overlapped (#81, #57). - Structures holding a
DateTimeor adecimalcould not be shared between runtimes that lay them out differently, such as .NET Framework and .NET 10, and arrays of them were copied with the wrong size on some runtimes (#81). - A garbage collection that moved an array during a read or write could make the copy use a stale address (#82).
SharedArray<T>.Write(T[], int)with astartIndexabove 0 wrote at element 0, from the wrong part of the source array, and some valid calls threwArgumentOutOfRangeException(#80).SharedArray<T>sizes and offsets could overflow: an array over 2 GB got the wrong size, and astartIndexnearInt32.MaxValuecould read the buffer's header (#83).List<T>.AddRange(sharedArray)threw on .NET 10 when the list already held items, andArraySlice<T>.ToArray()andToList()threwNotSupportedException(#83).FastStructure.CopyToandFromByteswrote or read past the end of a buffer too small for the structure, and threwIndexOutOfRangeExceptionfor empty arrays (#84).
.NET Framework builds
- The .NET Framework builds (
net35tonet47) threwEntryPointNotFoundExceptionat their first copy when loaded into .NET Core 3.0 or later, including .NET 5 to 10 (#74). The same cause lay behind #37 in Unity. net35: a buffer of 2 GB or more reserved more than its own size, 4 GB and a byte for a 2 GB buffer, and views at 4 GB or more did not map (#85).
Behaviour changes
RpcBuffer
- A send that cannot write a packet in time fails with
Success = false, where it used to drop the packet and report success. A receiver drops a message that lost a packet. timeoutMscovers the whole request: the send lock, the waits for room and the wait for the response. A reply still waits up to 1 s a packet for room. So does a request with a timeout of 0, which also waits for the send lock without limit.- Code after
await RemoteRequestAsync(...)runs on the thread pool, never on the RPC read thread. Disposenormally finishes within 100 ms, and requests still waiting for a response complete withSuccess = false. It no longer waits for running request handlers, so their replies are not sent.- The message ids a handler receives as
msgIdstart at a random 64-bit value in each instance, instead of 1. RpcStatistics.Timeoutscounts requests that timed out and packets that found no room.DiscardedResponsescounts replies that arrive after their request timed out, including every reply to a request sent with a timeout of 0.
CircularBuffer
new CircularBuffer(name), and so anRpcBufferclient, waits up to 1 s for an owner still creating the buffer, then throwsInvalidOperationException. It used to return an instance that could not read or write.- A writer waiting for room checks the ring every 10 ms, instead of relying on a signal alone.
SharedArray and FastStructure
FastStructure.SizeOf<T>()andFastStructure<T>.Sizereturn the size in shared memory: 1 byte forbool(was 4) and 2 forchar(was 1). Blittable types are unchanged.CircularBuffer.Read<T>andWrite<T>use this size too.- Single-structure reads and writes, such as the
SharedArray<T>indexer, copy throughFastStructureinstead of the view accessor. FastStructure.CopyToandFromBytesthrowArgumentExceptionwhen the structure does not fit in the buffer.SharedArray<T>.Write(T[], int)waits at the write gate, as the indexer does, where it used to write at once. While another instance holds the read lock, it waits up toReadWriteTimeout, then throwsTimeoutException.new SharedArray<T>(...)throwsTypeInitializationExceptionfor aTwith reference-type fields. 2.3.3 constructed some of these and failed at the first read or write.ICollection<T>.CopyTo, called through the interface, copies every element into the destination fromarrayIndex.SharedArray<T>.CopyTo(T[], int)called directly still starts reading atstartIndex.new SharedArray<T>(name, length)throwsArgumentOutOfRangeExceptionfor an array overInt32.MaxValuebytes, andnew SharedArray<T>(name)for one overInt32.MaxValueelements.WriteandCopyTothrow it when the range fromstartIndexdoes not fit.
Smaller changes
RpcBuffer: with atimeoutMsofTimeout.Infinite, the waits for the send lock, for room and for the response have no limit.RpcBuffer: a request with a timeout of 0 is sent and returnsSuccess = falsewithout waiting for the response.RpcBuffer: when a reply cannot be sent, its request waits out its timeout.RpcBuffer: a blockingRemoteRequeststill completes on the read thread, which runs none of its caller's code, unless a subclass overridesSendMessage.RpcBuffer.Disposewaits at most about 1 s. If the read thread or a send has not let go of the buffers by then, it returns and they close in the background.DisposeFinishedturns true once they have closed.RpcBuffer.Disposecuts short a reply being sent.- A new
RpcBufferon the same name, made straight after both ends are disposed, becomes the owner. - The
RpcBufferread thread checks for disposal every 100 ms, instead of every 500 ms. - Message ids count up by one from their random start.
RpcStatistics.LastDiscardedResponseIdholds these ids too. RpcStatistics.Timeoutsalso counts reply packets that found no room, and requests that got no send lock.FastStructure.SizeOf<T>()is the size of the layout 64-bit .NET 10 givesT: for example 32 bytes instead of 24 for anint, a Currencydecimaland anint. WhereTis copied field by field, that is not the size ofTin the process's own memory.- The single-structure reads and writes that copy through
FastStructureincludeBufferReadWrite.Read<T>(out T, long)andWrite<T>(ref T, long), andCircularBuffer.Read<T>(out T)andWrite<T>(ref T). They throw the same exception types as before for positions out of range. ICollection<T>.CopyToon aSharedArray<T>throwsArgumentExceptionif the elements do not fit. It used to copy as many elements as the destination held, from the shared array's elementarrayIndex.- Reflection reports
SharedArray<T>.CopyTo(T[], int)as non-virtual.
Speed
Time taken, against 2.3.3 on one Windows machine:
RpcBuffer |
.NET 10 | .NET Framework 4.8 |
|---|---|---|
| Awaited requests, one thread | about 20% less | 6 to 8% less |
| Awaited requests, many threads | about 80% less | about 40% less |
| Synchronous requests | within 3% | 1 to 6% less |
| Two-process round trips | 5 to 6% less | 5 to 6% less |
SharedArray<T>indexer: 6 to 14% less.CircularBufferping-pong and array copies of blittable types: unchanged, within 4%.- Array copies of structures the library copies field by field: up to about twice as long as a blittable structure of the same size, in a quick check on .NET Framework 4.8.