Fix RedisGrainDirectory uninitialize exception#9940
Fix RedisGrainDirectory uninitialize exception#9940miguelhasse wants to merge 3 commits intodotnet:mainfrom
Conversation
Updated the Uninitialize method to replace CloseAsync and synchronous Dispose calls with await _redis.DisposeAsync(), ensuring proper asynchronous disposal. Cleanup of _redis and _database fields is now handled in a finally block to guarantee resource release even if disposal fails.
There was a problem hiding this comment.
Pull request overview
Updates the Redis grain directory shutdown path to dispose the Redis connection asynchronously and ensure _redis/_database fields are cleared even if disposal throws, addressing a NullReferenceException during RedisGrainDirectory.Uninitialize.
Changes:
- Replace
CloseAsync()+ synchronousDispose()withawait _redis.DisposeAsync(). - Move cleanup of
_redisand_databaseinto afinallyblock to guarantee field cleanup.
| private async Task Uninitialize(CancellationToken arg) | ||
| { | ||
| if (_redis != null && _redis.IsConnected) | ||
| { | ||
| _disposed = true; | ||
|
|
||
| await _redis.CloseAsync(); | ||
| _redis.Dispose(); | ||
| _redis = null!; | ||
| _database = null!; | ||
| try | ||
| { | ||
| await _redis.DisposeAsync(); | ||
| } | ||
| finally | ||
| { | ||
| _redis = null!; | ||
| _database = null!; | ||
| } |
There was a problem hiding this comment.
This change targets a shutdown-time exception path, but there’s no coverage ensuring Uninitialize is safe to call when initialization is partial/failed or when it’s invoked multiple times. Consider adding a Redis grain directory test which exercises the shutdown path (e.g., invoke Uninitialize via reflection or lifecycle and assert it doesn’t throw).
Removed IsConnected check before disposing _redis and added unsubscription from ConnectionRestored, ConnectionFailed, ErrorMessage, and InternalError event handlers to prevent memory leaks and ensure proper cleanup.
|
What is the source of the |
From the logged exception it look like it it thrown from inside StackExchange's implementation, but you may be right about the possible race condition. Using an interlocked operation might be a good idea. |
Updated the Uninitialize method to replace CloseAsync and synchronous Dispose calls with await _redis.DisposeAsync(), ensuring proper asynchronous disposal. Cleanup of _redis and _database fields is now handled in a finally block to guarantee resource release even if disposal fails.
This change is a fix to avoid NullReferenceException being thrown during calls to RedisGrainDirectory .Uninitialize
Microsoft Reviewers: Open in CodeFlow