-
Notifications
You must be signed in to change notification settings - Fork 8
Fix the intermittent Interop unit test failures #369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8ee573c
c149c04
07953fd
fe6744d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -155,6 +155,14 @@ internal DataSourceManager DataSourceManager | |
| } | ||
| private LinkedList<RendererMessage> _messageQueue = new LinkedList<RendererMessage>(); | ||
|
|
||
| /// <summary> | ||
| /// Guards <see cref="_messageQueue"/> and the <c>_updateQueued</c> flag that decides when it | ||
| /// is flushed. Two paths flush it - <c>SendMessageImmediate</c> on the caller's thread, | ||
| /// <c>QueueUpdate</c> on the renderer's - and a component driven from off the Blazor | ||
| /// dispatcher (a timer, a bound collection filled in the background) hits both at once. | ||
| /// </summary> | ||
| private readonly object _messageQueueLock = new object(); | ||
|
|
||
| private string _containerId = Guid.NewGuid().ToString(); | ||
|
|
||
| internal string ContainerId | ||
|
|
@@ -852,7 +860,7 @@ private void SendDescriptionMessage() | |
| } | ||
| RendererMessage m = new RendererMessage(); | ||
| m.Type = ("description"); | ||
| _messageQueue.AddLast(m); | ||
| Enqueue(m); | ||
| QueueUpdate(); | ||
| } | ||
|
|
||
|
|
@@ -954,6 +962,13 @@ public string Serialize() | |
| private Dictionary<long, Object> _methodReturns = new Dictionary<long, Object>(); | ||
| private Object _semLock = new Object(); | ||
|
|
||
| /// <summary> | ||
| /// Shared by every instance because the WebView callback paths return only the invoke ID, | ||
| /// without a container ID to identify the originating component. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Only use <see cref="Interlocked.Increment" /> as this is incremented from any thread. | ||
| /// </remarks> | ||
| static long _invokeId = 0; | ||
| protected async Task<object> InvokeMethod(string methodName, object[] arguments, string[] types, ElementReference[] nativeElements = null) | ||
| { | ||
|
|
@@ -995,7 +1010,7 @@ internal object InvokeMethodHelperSync(string target, string methodName, object[ | |
| m.Type = ("invokeMethod"); | ||
| string[] args = new string[arguments.Length]; | ||
| string[] typeStrings = new string[arguments.Length]; | ||
| long invokeId = _invokeId++; | ||
| long invokeId = Interlocked.Increment(ref _invokeId); | ||
|
damyanpetev marked this conversation as resolved.
Dismissed
|
||
| for (int i = 0; i < arguments.Length; i++) | ||
| { | ||
| args[i] = GetStringArg(arguments[i], types[i]); | ||
|
|
@@ -1045,7 +1060,7 @@ internal async Task<object> InvokeMethodHelper(string target, string methodName, | |
| m.Type = ("invokeMethod"); | ||
| string[] args = new string[arguments.Length]; | ||
| string[] typeStrings = new string[arguments.Length]; | ||
| long invokeId = _invokeId++; | ||
| long invokeId = Interlocked.Increment(ref _invokeId); | ||
|
damyanpetev marked this conversation as resolved.
Dismissed
damyanpetev marked this conversation as resolved.
|
||
| for (int i = 0; i < arguments.Length; i++) | ||
| { | ||
| args[i] = GetStringArg(arguments[i], types[i]); | ||
|
|
@@ -1574,7 +1589,7 @@ private void SendMessage(RendererMessage m) | |
| return; | ||
| } | ||
| //Console.WriteLine("sending message"); | ||
| _messageQueue.AddLast(m); | ||
| Enqueue(m); | ||
| QueueUpdate(); | ||
| } | ||
|
|
||
|
|
@@ -1585,8 +1600,14 @@ private async Task<object> SendMessageImmediate(RendererMessage m) | |
| return null; | ||
| } | ||
|
|
||
| Update(); | ||
| return await SendJsonImmediate(m); | ||
| // The send must start under this lock. | ||
| Task<object> sent; | ||
| lock (_messageQueueLock) | ||
| { | ||
| Update(); | ||
| sent = SendJsonImmediate(m); | ||
| } | ||
| return await sent; | ||
| } | ||
|
|
||
| private object SendMessageSyncImmediate(RendererMessage m) | ||
|
|
@@ -1595,51 +1616,79 @@ private object SendMessageSyncImmediate(RendererMessage m) | |
| { | ||
| return null; | ||
| } | ||
| UpdateSync(); | ||
| return SendJsonImmediateSync(m); | ||
| lock (_messageQueueLock) | ||
| { | ||
| UpdateSync(); | ||
| return SendJsonImmediateSync(m); | ||
| } | ||
| } | ||
|
|
||
| private void QueueUpdate() | ||
| private void Enqueue(RendererMessage m) | ||
| { | ||
| if (!_updateQueued && _ready) | ||
| lock (_messageQueueLock) | ||
| { | ||
| _updateQueued = true; | ||
| Task.Delay(0).ContinueWith((t) => InvokeAsync(Update)); | ||
| _messageQueue.AddLast(m); | ||
| } | ||
| } | ||
|
|
||
| private void Update() | ||
| private void QueueUpdate() | ||
| { | ||
| this._updateQueued = false; | ||
|
|
||
| if (!_ready) | ||
| bool schedule = false; | ||
| lock (_messageQueueLock) | ||
| { | ||
| return; | ||
| if (!_updateQueued && _ready) | ||
| { | ||
| _updateQueued = true; | ||
| schedule = true; | ||
| } | ||
| } | ||
|
|
||
| //Console.WriteLine("updateing: " + this.GetType().Name + " " + _messageQueue.Count); | ||
| while (_messageQueue.Count > 0) | ||
| if (schedule) | ||
| { | ||
| RendererMessage m = _messageQueue.First.Value; | ||
| _messageQueue.RemoveFirst(); | ||
| ProcessMessage(m); | ||
| Task.Delay(0).ContinueWith((t) => InvokeAsync(Update)); | ||
| } | ||
| } | ||
|
|
||
| private void UpdateSync() | ||
| private void Update() | ||
| { | ||
| this._updateQueued = false; | ||
|
|
||
| if (!_ready) | ||
| // Spans the whole drain, not just the dequeue, so two flushes cannot interleave their | ||
| // sends. A thread already holding it can take it again, so nesting is fine. | ||
| lock (_messageQueueLock) | ||
| { | ||
| return; | ||
| this._updateQueued = false; | ||
|
|
||
| if (!_ready) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| //Console.WriteLine("updateing: " + this.GetType().Name + " " + _messageQueue.Count); | ||
| while (_messageQueue.Count > 0) | ||
| { | ||
| RendererMessage m = _messageQueue.First.Value; | ||
| _messageQueue.RemoveFirst(); | ||
| ProcessMessage(m); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| while (_messageQueue.Count > 0) | ||
| private void UpdateSync() | ||
| { | ||
| lock (_messageQueueLock) | ||
| { | ||
| RendererMessage m = _messageQueue.First.Value; | ||
| _messageQueue.RemoveFirst(); | ||
| ProcessMessageSync(m); | ||
| this._updateQueued = false; | ||
|
|
||
| if (!_ready) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| while (_messageQueue.Count > 0) | ||
| { | ||
| RendererMessage m = _messageQueue.First.Value; | ||
| _messageQueue.RemoveFirst(); | ||
| ProcessMessageSync(m); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -3177,7 +3226,10 @@ private async Task TrySendCleanupAsync() | |
| RendererMessage m = new RendererMessage(); | ||
| m.Type = ("cleanup"); | ||
|
|
||
| _messageQueue.Clear(); | ||
| lock (_messageQueueLock) | ||
| { | ||
| _messageQueue.Clear(); | ||
| } | ||
|
Comment on lines
+3229
to
+3232
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is almost going a bit out of scope, but will address. disposedValue = true;
_shouldReevaluateRuntime = true;
await TrySendCleanupAsync(); // → SendMessageImmediate → if (disposedValue) return null;That didn't show up on the diff for #335 and I completely missed it too, but it's quite correct. Doesn't help that all the tests are also of the "doesn't throw" variety, which of course it doesn't do when not sending anything as well :D
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved the additional fixes (they keep pilling up) and the test in question in #394 |
||
| await SendMessageImmediate(m).ConfigureAwait(false); | ||
| } | ||
| catch (JSDisconnectedException ex) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.