Refactor type readiness check to avoid NullReferenceException
#3121
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
We encountered a issue where our service would throw a
NullReferenceException
during startup, necessitating a reboot for recovery. This problem arises particularly when multiple operations execute in parallel at startup, such asEvents.AggregateStreamAsync<T>
andEvents.Append(...) + SaveChanges
. This situation triggers the compilation/build of generated types through different execution paths but not in a synchronized manner.Through debugging, we observed that the setters for our aggregate in the generated document were not set when the error occurred. This led us to scrutinize the compile/build code, particularly the assembleTypes method, where the essential processing happens. We identified a specific flaw where the lock code checks the
_liveGeneratedType
property, but the setters are assigned afterward. This discrepancy creates a window where the code incorrectly assumes the compilation/build process is complete, proceeding to operations that depend on setters that have not yet been initialized.The fix involves changing the source of truth for checking if types are ready from
_liveType
and_liveGeneratedType
(which are set mid-process and hence unreliable) to a more dependable flag:_hasGenerated
. This change ensures a more accurate and consistent readiness check, preventing the premature usage of types.Example from our debug where we can see that
BuildLiveAggregator
is called before the setters are ready ("Setters is now set").The exception:
Test
I managed to create a reproducible test through a workaround that involved inserting a sleep immediately after setting
_liveGeneratedType
. This approach also necessitated having events pre-persisted before executing the test to simulate the real-world scenario accurately. While this test is not included, it is accessible for review at the following URL: Leh2@246c83c.It's important to note that in our implementation, we leverage private apply methods, which exhibit somewhat different behavior in the generated document.