Skip to content

Commit eced49b

Browse files
committed
Wording pass over the prose this branch adds
Shorter sentences and plainer verbs in the README, CHANGELOG, AspNetCore and Micro READMEs; no figure, identifier or link changed. Two factual slips fixed on the way: the Kestrel introduction no longer says every row ran with EnableAsyncMethods = false, and the Micro README's 0 B claim is scoped to the inline None rows.
1 parent 0c85c52 commit eced49b

4 files changed

Lines changed: 37 additions & 37 deletions

File tree

‎AustinHarris.JsonRpc.AspNetCore/README.md‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,16 @@ app.MapJsonRpc("/legacy", new JsonRpcOptions { SessionId = "legacy-clients", Ser
7575
## Services and lifetime
7676

7777
`AddJsonRpcService<T>()` registers `T` as a singleton unless `T` is already registered. When the host starts,
78-
each registered service is resolved once from the root container and bound; that one instance then serves every
79-
HTTP request and every raw connection, concurrently. So `T` and its dependencies must be thread-safe, and `T`
80-
cannot take scoped dependencies such as an EF Core `DbContext`: with scope validation on, the host fails at
81-
startup; with it off, the dependency leaks. For per-request services, resolve them inside the method from
82-
`((HttpContext)Handler.RpcContext()).RequestServices` on HTTP; a raw connection's context is the
83-
`ConnectionContext`, which has no request scope. Do not inject request-scoped state into a service; read
84-
per-request data from the context instead.
78+
it resolves each registered service once from the root container and binds it. That one instance then serves
79+
every HTTP request and every raw connection concurrently. So `T` and its dependencies must be thread-safe.
80+
`T` cannot take scoped dependencies such as an EF Core `DbContext`. With scope validation on, the host fails
81+
at startup. With it off, the dependency leaks. On HTTP, resolve per-request services inside the method from
82+
`((HttpContext)Handler.RpcContext()).RequestServices`. A raw connection's context is the
83+
`ConnectionContext`, which has no request scope. Do not inject request-scoped state into a service.
84+
Read per-request data from the context instead.
8585

8686
`AddJsonRpcServicesFromAssembly(assembly)` does the same for every non-abstract class in the assembly that
87-
declares a `[JsonRpcMethod]`. Private methods count, so the attribute is the whole access list, and an MVC
87+
declares a `[JsonRpcMethod]`. Private methods count, so the attribute is the whole access list. An MVC
8888
controller that carries it becomes a singleton too.
8989

9090
The host binds every registered service to its effective session: the session given to `AddJsonRpcService`, else
@@ -123,13 +123,13 @@ invoked.
123123

124124
- **HTTP:** the call is cancelled when the client disconnects (`HttpContext.RequestAborted`). Notifications are
125125
awaited and still answer `204`. The body reader stays leased until the invocation finishes.
126-
- **Raw connections:** documents are processed one at a time, in order, so 256 pipelined requests on one
127-
connection are 256 sequential invocations, not 256 concurrent suspensions; concurrency comes from connections.
126+
- **Raw connections:** documents are processed one at a time, in order. On one connection, 256 pipelined requests
127+
are 256 sequential invocations, not 256 concurrent suspensions. Concurrency comes from connections.
128128
Replies already finished are flushed before the connection waits on a slow method. When the connection closes,
129-
the running method is waited for and its response discarded.
130-
- **Cost:** every document then goes through `ProcessAsync`. With methods that complete inline the TCP row measures
131-
about 7 % below the synchronous mode (15.4 M against 16.5 M); a method that really suspends pays its own async state plus the
132-
library's completion state (about 560 B) and a continuation per request. The main README's Kestrel table has
129+
the handler waits for the running method to finish and discards its response.
130+
- **Cost:** every document then goes through `ProcessAsync`. With methods that complete inline, the TCP row measures
131+
about 7 % below the synchronous mode (15.4 M against 16.5 M). A method that suspends pays for its own async state,
132+
the library's completion state (about 560 B) and a continuation per request. The main README's Kestrel table has
133133
both rows, measured with `TestServer_Console --kestrel 3 async`.
134134

135135
A method receives the token by declaring a `[JsonRpcCancellation] CancellationToken` parameter; see

‎CHANGELOG.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ behaviour: a breaking change to either means a new major version.
2323
- `Config.SetPreProcessHandler(sessionId, …)` and `Config.SetPostProcessHandler(sessionId, …)`, symmetric with the default-session setters. `Config.SetBeforeProcessHandler(sessionId, …)` remains as an obsolete alias.
2424
- `protected JsonRpcService(bool autoBind)`: a subclass constructed with `base(false)` binds itself nowhere, for services that a host or an explicit `BindService` call binds.
2525
- `SECURITY.md` (private vulnerability reporting) and this changelog.
26-
- `TestServer_Console --scale`: the release gate for the `ProcessAsync` path (the inline rows at 1, 2 and N workers, three paired runs, medians; fails when N/1 is below the threshold), `--kestrel [seconds] async` for the host with `EnableAsyncMethods = true`, and `--async` rows for `ProcessAsync` at 1 and 16 workers in the README. The 1.x string overloads' thread-pool benchmark is the `t` menu entry, no longer the default.
26+
- `TestServer_Console --scale` is the release gate for the `ProcessAsync` path. It measures the inline rows at 1, 2 and N workers in three paired runs, takes the medians and fails when N/1 is below the threshold. `--kestrel [seconds] async` runs the host with `EnableAsyncMethods = true`. The README adds `--async` rows for `ProcessAsync` at 1 and 16 workers. The 1.x string overloads' thread-pool benchmark is now the `t` menu entry and no longer the default.
2727

2828
### Changed
2929

3030
- The core no longer depends on Json.NET.
31-
- `ProcessAsync` no longer serializes the process on one lock per document. The async scratch (input copy, reader, staged output) is cached one per thread in front of the shared pool, which is now the miss and overflow path only; on 16 threads the inline rows went from about 4 M to 22 M to 32 M RPC/s and a real suspension from 3.9 M to 9 M. Retention is one scratch per thread that has run `ProcessAsync` plus 64 shared, buffers at most 64 KiB each.
31+
- `ProcessAsync` no longer serializes the process on one lock per document. Each thread now caches one async scratch (input copy, reader, staged output) in front of the shared pool, which handles only misses and overflow. On 16 threads, the inline rows went from about 4 M to 22 M to 32 M RPC/s, and the row with a real suspension went from 3.9 M to 9 M. The library retains one scratch per thread that has run `ProcessAsync` plus 64 shared, with buffers at most 64 KiB each.
3232
- The request path looks sessions up without creating them. A request for a session id that was never registered answers `-32601` for every call and leaves the registry untouched; sessions are created by binding and by the per-session `Config` setters. Registration adds the session before publishing the registry version, so a thread that misses its snapshot consults the master registry and cannot answer `-32601` for a session that exists.
3333
- The AspNetCore host binds every registered service, `JsonRpcService` subclasses included, to its effective session (the registration's session, then `JsonRpcOptions.SessionId`, then the default). It no longer skips a subclass on the default session.
3434
- The core package's description says "no JSON library dependency" instead of "no dependencies". The session registry uses the framework's `ConcurrentDictionary`; the `NonBlocking` package reference is gone, so the core has no dependencies on `net8.0` and `net10.0` (measured with `SessionRegistryBenchmarks`: unknown-id lookups and register/destroy cycles got faster, stable lookups and dispatch are unchanged).
@@ -49,7 +49,7 @@ behaviour: a breaking change to either means a new major version.
4949

5050
- A trailing notification in a batch no longer leaves a dangling comma.
5151
- `async void` methods are rejected at registration.
52-
- A method registered with `RpcContextFlow.Flow` that suspends under a pre- or post-processing hook and completes on another thread no longer hands that thread the frame of the thread that started it. Afterwards the two threads shared one frame, so `RpcContext`, `RpcRequestId` and `RpcSetException` on either could read or clear the other's while both dispatched. Found by the new cross-thread `ProcessAsync` test; the frame is now restored through the ambient value alone.
52+
- A method registered with `RpcContextFlow.Flow` that suspends under a pre- or post-processing hook and completes on another thread no longer gives the completing thread the starting thread's frame. Previously, the two threads then shared one frame, so `RpcContext`, `RpcRequestId` and `RpcSetException` on either could read or clear the other's state during concurrent dispatch. The new cross-thread `ProcessAsync` test found this bug. The frame is now restored through the ambient value alone.
5353

5454
### Security
5555

0 commit comments

Comments
 (0)