allthingssecurity commented on PR #29528:
URL: https://github.com/apache/camel/pull/29528#issuecomment-6769631120
Thanks. 7364b429a3f4 has the soft share and the docs for the parallel split.
**1. Parallel split.** I ran the row you asked for: one producer thread, a
parallel split of 16 parts (2 steps each) on a pool of 1, 4 or 8 threads,
`stackDepth=0`. Same settings as before: 2 forks, 3 × 2 s warmup, 5 × 2 s,
`-Xmx1g`. Values are ns per message (± 99.9% CI). After each trial the table
was empty, nothing was dropped, and there was no warning.
| pool | off | t0 | t60 | t60 − t0 | t60 / t0 |
|---|---|---|---|---|---|
| 1 | 18,576 ± 514 | 20,039 ± 402 | 27,114 ± 645 | 7.1 µs | 1.35 |
| 4 | 27,833 ± 609 | 31,074 ± 460 | 43,911 ± 979 | 12.8 µs | 1.41 |
| 8 | 33,181 ± 1,304 | 33,971 ± 862 | 46,796 ± 1,503 | 12.8 µs | 1.38 |
Thread dumps: 12 jstack samples during measurement.
- Pool of 8, `t60`: 0 of 96 pool-thread samples were BLOCKED. 85 were parked
waiting for a task.
- Pool of 8, `t0`: 0 of 96 BLOCKED, 88 parked.
- Pool of 4, `t60`: 0 of 48 BLOCKED, 36 parked.
With one producer the pool is idle most of the time, so two part threads
rarely want the stripe at the same moment. The samples catch only BLOCKED
threads, not a short spin before a thread blocks.
What this shows:
- This is not the one-lock pattern of 39a5da11b25b. No thread waits on the
stripe, and the ratio to `t0` stays the same from 1 to 8 threads.
- The table does cost more when the parts run on several threads: 12.8 µs
per message instead of 7.1 µs. The stripe's lock and data move between cores.
- More important for race hunting: the lock orders the part threads. Each
step call, return and callback is a synchronization point between them, which
can hide a race between parts that is not synchronized otherwise.
Giving the parts their own stripe is not a small change. The parent link,
`touch()` of the parent's activity time, the cascade of `unlink`,
`done`/`dropThrown` and the "innermost only" check would all cross stripes.
Each part event would still have to update the parent's activity time, and the
model would have to be rebuilt around that. So I did not do it. Instead, the
docs page ("Still pending" and the option table, catalog copy identical) and
the javadoc of `pendingTimeout` (`main.adoc`, the metadata and their catalog
copies) now say: when hunting races between the parts of a parallel split,
multicast or recipient list, set `pendingTimeout=0`. The other R1 checks stay
on.
**2. Soft share.** Done as you suggested.
- A stripe within its share never looks at the other stripes.
- Past its share, it sums the stripe sizes once without their locks (`size`
is volatile) and drops its oldest calls only while the excess over 10 000 lasts.
- The table then holds at most 10 000 plus the shares of the other stripes
(give or take the calls being made at that moment). The WARN now names the
bound of the table, not a stripe.
Tests:
- 4 × 312 parts of one exchange in one of 32 stripes are all kept and each
is reported; past 10 000, the oldest are dropped and counted as before.
- The stripes together keep the bound.
- 8 threads past the bound on 4 stripes: every call is kept or counted,
there is one first drop, and the bound holds.
The first two tests fail on 4159536c0894.
The TLA+ model has the soft bound (share 1, bound 3), a new mutant (drop at
the share, as before) that is caught, and two new invariants (the bound; no
drop while the table is within the bound). A small spec of its own reads the
sizes one stripe at a time while other threads change them:
- the loop always ends;
- the bound holds once no call is in progress;
- with more than one thread, a few calls more can be dropped near the bound,
as expected for a sum read without the locks.
| config | result | distinct states |
|---|---|---|
| main | no error | 5,127,828 |
| timeout of 2 ticks | no error | 12,817,344 |
| `shareUnitOfWork` parts | no error | 2,520,588 |
| `pendingTimeout=0` | no error | 50,028 |
| liveness | no error | 1,166,352 |
| P and Q in two stripes (simulation, 7 min) | no error | 448,818 traces |
| soft bound, 1 thread / 2 threads | no error | 436 / 81,208 |
All mutants are still caught, eight now.
**3.**
- The benchmark is in apache/camel-performance-tests#162 (draft). It covers
the routes `steps`, `split`, and a parallel split on 4 or 8 threads, the modes
off/t0/t60, `stackDepth` 0 and 32, and 1 or 4 producer threads. It is in a
profile, `sanitizer`, and stays a draft until a 4.24.0-SNAPSHOT with
`pendingTimeout` is published.
- The two TLA+ specs will be on CAMEL-26751.
**4.** The `stackDepth=32` cost is CAMEL-26812.
**Tests.** The sanitizer tests of camel-core, camel-management, camel-main
and camel-test-main-junit6, plus `SimpleEventNotifierEventsTest`, pass with and
without `-Psanitizer` (90 tests, 0 failures each). `SanitizerPendingCallsTest`
and `SanitizerStillPendingTest` passed 20 times in one JVM, with and without
the switch: 680/680 each.
_Claude Code on behalf of allthingssecurity_
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]