A value evaluated under a raised precision in one flow is returned to another flow that is still at the default precision:
var evaluated = new ManualResetEventSlim();
var release = new ManualResetEventSlim();
var other = Task.Run(() =>
{
using var _ = MathS.Settings.DecimalPrecisionContext.Set(new EContext(200, ERounding.HalfUp, -1000, 1000, false));
var high = MathS.pi.Evaled;
evaluated.Set();
release.Wait();
});
evaluated.Wait();
Console.WriteLine(MathS.Settings.DecimalPrecisionContext.Value.Precision); // 100
Console.WriteLine(MathS.pi.Evaled.ToString().Length - 2); // 199 digits
release.Set();
Once the other flow's scope closes, the same read gives 99 digits again. This happens on master (c9f688b7) in three runs out of three.
It is what failed #1504's first CI run: DocumentationExamplesRunTest.EveryDocumentedExamplePrintsWhatItSaysItPrints got pi at more than 200 digits in the example on MathS.i, while the test suite ran other classes in parallel. On rerun it passed.
The cause is in Evaled's cache. The value is stamped with EvaluationEpoch.Current, a counter shared by the whole process, while the precision it was computed at belongs to one flow (Setting<T> is an AsyncLocal). One flow opens a precision scope, which advances the epoch, and evaluates a node another flow shares, such as the static MathS.pi. From then until something advances the epoch again, every flow gets that value back. EvaluationEpoch's remark that a change on another thread costs "a recomputation and never a stale value" holds for the change itself, but not for a computation made after the change.
The fix I'd propose is to stamp the cached value with the precision frame it was computed under, not only the epoch. Every Setting frame already has a process-unique Id. A flow in another precision scope would then recompute instead of reading. The cost is one AsyncLocal read per Evaled, against a static read now, so I'd measure EvalEasy and the others before and after.
A value evaluated under a raised precision in one flow is returned to another flow that is still at the default precision:
Once the other flow's scope closes, the same read gives 99 digits again. This happens on master (
c9f688b7) in three runs out of three.It is what failed #1504's first CI run:
DocumentationExamplesRunTest.EveryDocumentedExamplePrintsWhatItSaysItPrintsgotpiat more than 200 digits in the example onMathS.i, while the test suite ran other classes in parallel. On rerun it passed.The cause is in
Evaled's cache. The value is stamped withEvaluationEpoch.Current, a counter shared by the whole process, while the precision it was computed at belongs to one flow (Setting<T>is anAsyncLocal). One flow opens a precision scope, which advances the epoch, and evaluates a node another flow shares, such as the staticMathS.pi. From then until something advances the epoch again, every flow gets that value back.EvaluationEpoch's remark that a change on another thread costs "a recomputation and never a stale value" holds for the change itself, but not for a computation made after the change.The fix I'd propose is to stamp the cached value with the precision frame it was computed under, not only the epoch. Every
Settingframe already has a process-uniqueId. A flow in another precision scope would then recompute instead of reading. The cost is oneAsyncLocalread perEvaled, against a static read now, so I'd measureEvalEasyand the others before and after.