Skip to content

Commit e55dc2e

Browse files
authored
Improve internal exception handling (#753)
1 parent 92cc6c7 commit e55dc2e

File tree

18 files changed

+357
-222
lines changed

18 files changed

+357
-222
lines changed

lib/PuppeteerSharp.Tests/BrowserTests/Events/DisconnectedTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public async Task ShouldRejectWaitForSelectorWhenBrowserCloses()
9090
//Whether from the Connection rejecting a message from the CDPSession
9191
//or from the CDPSession trying to send a message to a closed connection
9292
Assert.IsType<TargetClosedException>(exception.InnerException);
93+
Assert.Equal("Connection disposed", ((TargetClosedException)exception.InnerException).CloseReason);
9394
}
9495
}
9596
}

lib/PuppeteerSharp.Tests/PageTests/CloseTests.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ public CloseTests(ITestOutputHelper output) : base(output) { }
1313
public async Task ShouldRejectAllPromisesWhenPageIsClosed()
1414
{
1515
var neverResolves = Page.EvaluateFunctionAsync("() => new Promise(r => {})");
16-
17-
// Put into a var to avoid warning
18-
var t = Page.CloseAsync();
16+
_ = Page.CloseAsync();
1917

2018
var exception = await Assert.ThrowsAsync<EvaluationFailedException>(async () => await neverResolves);
2119
Assert.IsType<TargetClosedException>(exception.InnerException);
2220
Assert.Contains("Protocol error", exception.Message);
21+
Assert.Equal("Target.detachedFromTarget", ((TargetClosedException)exception.InnerException).CloseReason);
2322
}
2423

2524
[Fact]

lib/PuppeteerSharp/Browser.cs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -392,19 +392,28 @@ internal async Task DisposeContextAsync(string contextId)
392392

393393
private async void Connect_MessageReceived(object sender, MessageEventArgs e)
394394
{
395-
switch (e.MessageID)
395+
try
396396
{
397-
case "Target.targetCreated":
398-
await CreateTargetAsync(e.MessageData.ToObject<TargetCreatedResponse>()).ConfigureAwait(false);
399-
return;
397+
switch (e.MessageID)
398+
{
399+
case "Target.targetCreated":
400+
await CreateTargetAsync(e.MessageData.ToObject<TargetCreatedResponse>()).ConfigureAwait(false);
401+
return;
400402

401-
case "Target.targetDestroyed":
402-
await DestroyTargetAsync(e.MessageData.ToObject<TargetDestroyedResponse>()).ConfigureAwait(false);
403-
return;
403+
case "Target.targetDestroyed":
404+
await DestroyTargetAsync(e.MessageData.ToObject<TargetDestroyedResponse>()).ConfigureAwait(false);
405+
return;
404406

405-
case "Target.targetInfoChanged":
406-
ChangeTargetInfo(e.MessageData.ToObject<TargetCreatedResponse>());
407-
return;
407+
case "Target.targetInfoChanged":
408+
ChangeTargetInfo(e.MessageData.ToObject<TargetCreatedResponse>());
409+
return;
410+
}
411+
}
412+
catch (Exception ex)
413+
{
414+
var message = $"Browser failed to process {e.MessageID}. {ex.Message}. {ex.StackTrace}";
415+
_logger.LogError(ex, message);
416+
Connection.Close(message);
408417
}
409418
}
410419

lib/PuppeteerSharp/CDPSession.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,10 @@ internal CDPSession(IConnection connection, TargetType targetType, string sessio
9292
/// </summary>
9393
/// <value><c>true</c> if is closed; otherwise, <c>false</c>.</value>
9494
public bool IsClosed { get; internal set; }
95-
95+
/// <summary>
96+
/// Connection close reason.
97+
/// </summary>
98+
public string CloseReason { get; private set; }
9699
/// <summary>
97100
/// Gets the logger factory.
98101
/// </summary>
@@ -133,7 +136,10 @@ public async Task<JObject> SendAsync(string method, dynamic args = null, bool wa
133136
{
134137
if (Connection == null)
135138
{
136-
throw new PuppeteerException($"Protocol error ({method}): Session closed. Most likely the {TargetType} has been closed.");
139+
throw new PuppeteerException(
140+
$"Protocol error ({method}): Session closed. " +
141+
$"Most likely the {TargetType} has been closed." +
142+
$"Close reason: {CloseReason}");
137143
}
138144
var id = Interlocked.Increment(ref _lastId);
139145
var message = JsonConvert.SerializeObject(new Dictionary<string, object>
@@ -252,7 +258,7 @@ internal void OnMessage(string message)
252258

253259
if (_sessions.TryRemove(sessionId, out var session))
254260
{
255-
session.OnClosed();
261+
session.Close("Target.detachedFromTarget");
256262
}
257263
}
258264

@@ -264,24 +270,26 @@ internal void OnMessage(string message)
264270
}
265271
}
266272

267-
internal void OnClosed()
273+
internal void Close(string closeReason)
268274
{
269275
if (IsClosed)
270276
{
271277
return;
272278
}
279+
CloseReason = closeReason;
273280
IsClosed = true;
274281

275282
foreach (var session in _sessions.Values.ToArray())
276283
{
277-
session.OnClosed();
284+
session.Close(closeReason);
278285
}
279286
_sessions.Clear();
280287

281288
foreach (var callback in _callbacks.Values.ToArray())
282289
{
283290
callback.TaskWrapper.TrySetException(new TargetClosedException(
284-
$"Protocol error({callback.Method}): Target closed."
291+
$"Protocol error({callback.Method}): Target closed.",
292+
closeReason
285293
));
286294
}
287295
_callbacks.Clear();
@@ -303,6 +311,7 @@ internal CDPSession CreateSession(TargetType targetType, string sessionId)
303311
Task<JObject> IConnection.SendAsync(string method, dynamic args, bool waitForCallback)
304312
=> SendAsync(method, args, waitForCallback);
305313
IConnection IConnection.Connection => Connection;
314+
void IConnection.Close(string closeReason) => Close(closeReason);
306315
#endregion
307316
}
308317
}

lib/PuppeteerSharp/Connection.cs

Lines changed: 74 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ internal Connection(string url, int delay, IConnectionTransport transport, ILogg
7474
/// <value><c>true</c> if is closed; otherwise, <c>false</c>.</value>
7575
public bool IsClosed { get; internal set; }
7676

77+
/// <summary>
78+
/// Connection close reason.
79+
/// </summary>
80+
public string CloseReason { get; private set; }
81+
7782
/// <summary>
7883
/// Gets the logger factory.
7984
/// </summary>
@@ -88,7 +93,7 @@ internal async Task<JObject> SendAsync(string method, dynamic args = null, bool
8893
{
8994
if (IsClosed)
9095
{
91-
throw new TargetClosedException($"Protocol error({method}): Target closed.");
96+
throw new TargetClosedException($"Protocol error({method}): Target closed.", CloseReason);
9297
}
9398

9499
var id = Interlocked.Increment(ref _lastId);
@@ -137,27 +142,29 @@ internal async Task<CDPSession> CreateSessionAsync(TargetInfo targetInfo)
137142
internal bool HasPendingCallbacks() => _callbacks.Count != 0;
138143
#endregion
139144

140-
private void OnClose()
145+
internal void Close(string closeReason)
141146
{
142147
if (IsClosed)
143148
{
144149
return;
145150
}
146151
IsClosed = true;
152+
CloseReason = closeReason;
147153

148154
Transport.StopReading();
149155
Closed?.Invoke(this, new EventArgs());
150156

151157
foreach (var session in _sessions.Values.ToArray())
152158
{
153-
session.OnClosed();
159+
session.Close(closeReason);
154160
}
155161
_sessions.Clear();
156162

157163
foreach (var response in _callbacks.Values.ToArray())
158164
{
159165
response.TaskWrapper.TrySetException(new TargetClosedException(
160-
$"Protocol error({response.Method}): Target closed."
166+
$"Protocol error({response.Method}): Target closed.",
167+
closeReason
161168
));
162169
}
163170
_callbacks.Clear();
@@ -176,77 +183,86 @@ internal static IConnection FromSession(CDPSession session)
176183

177184
private async void Transport_MessageReceived(object sender, MessageReceivedEventArgs e)
178185
{
179-
var response = e.Message;
180-
JObject obj = null;
181-
182-
if (response.Length > 0 && Delay > 0)
183-
{
184-
await Task.Delay(Delay).ConfigureAwait(false);
185-
}
186-
187186
try
188187
{
189-
obj = JObject.Parse(response);
190-
}
191-
catch (JsonException exc)
192-
{
193-
_logger.LogError(exc, "Failed to deserialize response", response);
194-
return;
195-
}
196-
197-
_logger.LogTrace("◀ Receive {Message}", response);
198-
199-
var id = obj[MessageKeys.Id]?.Value<int>();
188+
var response = e.Message;
189+
JObject obj = null;
200190

201-
if (id.HasValue)
202-
{
203-
//If we get the object we are waiting for we return if
204-
//if not we add this to the list, sooner or later some one will come for it
205-
if (_callbacks.TryRemove(id.Value, out var callback))
191+
if (response.Length > 0 && Delay > 0)
206192
{
207-
if (obj[MessageKeys.Error] != null)
208-
{
209-
callback.TaskWrapper.TrySetException(new MessageException(callback, obj));
210-
}
211-
else
212-
{
213-
callback.TaskWrapper.TrySetResult(obj[MessageKeys.Result].Value<JObject>());
214-
}
193+
await Task.Delay(Delay).ConfigureAwait(false);
215194
}
216-
}
217-
else
218-
{
219-
var method = obj[MessageKeys.Method].AsString();
220-
var param = obj[MessageKeys.Params];
221195

222-
if (method == "Target.receivedMessageFromTarget")
196+
try
223197
{
224-
var sessionId = param[MessageKeys.SessionId].AsString();
225-
if (_sessions.TryGetValue(sessionId, out var session))
226-
{
227-
session.OnMessage(param[MessageKeys.Message].AsString());
228-
}
198+
obj = JObject.Parse(response);
229199
}
230-
else if (method == "Target.detachedFromTarget")
200+
catch (JsonException exc)
201+
{
202+
_logger.LogError(exc, "Failed to deserialize response", response);
203+
return;
204+
}
205+
206+
_logger.LogTrace("◀ Receive {Message}", response);
207+
208+
var id = obj[MessageKeys.Id]?.Value<int>();
209+
210+
if (id.HasValue)
231211
{
232-
var sessionId = param[MessageKeys.SessionId].AsString();
233-
if (_sessions.TryRemove(sessionId, out var session) && !session.IsClosed)
212+
//If we get the object we are waiting for we return if
213+
//if not we add this to the list, sooner or later some one will come for it
214+
if (_callbacks.TryRemove(id.Value, out var callback))
234215
{
235-
session.OnClosed();
216+
if (obj[MessageKeys.Error] != null)
217+
{
218+
callback.TaskWrapper.TrySetException(new MessageException(callback, obj));
219+
}
220+
else
221+
{
222+
callback.TaskWrapper.TrySetResult(obj[MessageKeys.Result].Value<JObject>());
223+
}
236224
}
237225
}
238226
else
239227
{
240-
MessageReceived?.Invoke(this, new MessageEventArgs
228+
var method = obj[MessageKeys.Method].AsString();
229+
var param = obj[MessageKeys.Params];
230+
231+
if (method == "Target.receivedMessageFromTarget")
232+
{
233+
var sessionId = param[MessageKeys.SessionId].AsString();
234+
if (_sessions.TryGetValue(sessionId, out var session))
235+
{
236+
session.OnMessage(param[MessageKeys.Message].AsString());
237+
}
238+
}
239+
else if (method == "Target.detachedFromTarget")
241240
{
242-
MessageID = method,
243-
MessageData = param
244-
});
241+
var sessionId = param[MessageKeys.SessionId].AsString();
242+
if (_sessions.TryRemove(sessionId, out var session) && !session.IsClosed)
243+
{
244+
session.Close("Target.detachedFromTarget");
245+
}
246+
}
247+
else
248+
{
249+
MessageReceived?.Invoke(this, new MessageEventArgs
250+
{
251+
MessageID = method,
252+
MessageData = param
253+
});
254+
}
245255
}
246256
}
257+
catch (Exception ex)
258+
{
259+
var message = $"Connection failed to process {e.Message}. {ex.Message}. {ex.StackTrace}";
260+
_logger.LogError(ex, message);
261+
Close(message);
262+
}
247263
}
248264

249-
void Transport_Closed(object sender, EventArgs e) => OnClose();
265+
void Transport_Closed(object sender, TransportClosedEventArgs e) => Close(e.CloseReason);
250266

251267
#endregion
252268

@@ -290,7 +306,7 @@ internal static async Task<Connection> Create(string url, IConnectionOptions con
290306
/// <see cref="Connection"/> was occupying.</remarks>
291307
public void Dispose()
292308
{
293-
OnClose();
309+
Close("Connection disposed");
294310
Transport.Dispose();
295311
}
296312
#endregion
@@ -301,6 +317,7 @@ public void Dispose()
301317
Task<JObject> IConnection.SendAsync(string method, dynamic args, bool waitForCallback)
302318
=> SendAsync(method, args, waitForCallback);
303319
IConnection IConnection.Connection => null;
320+
void IConnection.Close(string closeReason) => Close(closeReason);
304321
#endregion
305322
}
306323
}

0 commit comments

Comments
 (0)