Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# 1.1.1

- Fixed background thread leaks when worker construction is rejected
- Fixed canonical DOM node identity and concrete prototypes through `DomSameObject` getters (#140)

# 1.1.0
Expand Down
32 changes: 31 additions & 1 deletion src/AngleSharp.Js.Tests/WorkerTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
namespace AngleSharp.Js.Tests
{
using AngleSharp.Dom;
using AngleSharp.Io;
using AngleSharp.Js.Tests.Mocks;
using NUnit.Framework;
using System;
using System.Diagnostics;
using System.Threading.Tasks;

[TestFixture]
Expand Down Expand Up @@ -90,6 +92,34 @@ public async Task WorkerShouldRequireResourceLoading()
Assert.AreEqual("true", result);
}

[Test]
[NonParallelizable]
public async Task RejectedWorkersShouldNotLeaveRunningThreads()
{
using (var context = BrowsingContext.New(Configuration.Default.WithJs()))
{
var document = await context.OpenAsync(request => request.Content("<!doctype html>")).ConfigureAwait(false);
Assert.Throws<DomException>(() => new AngleSharp.Js.Dom.Worker(document.DefaultView, "/worker.js"));
var before = CountProcessThreads();

for (var i = 0; i < 32; i++)
{
Assert.Throws<DomException>(() => new AngleSharp.Js.Dom.Worker(document.DefaultView, "/worker.js"));
}

// Allow unrelated runtime threads without hiding one surviving loop per failure.
Assert.Less(CountProcessThreads() - before, 16);
}
}

private static Int32 CountProcessThreads()
{
using (var process = Process.GetCurrentProcess())
{
return process.Threads.Count;
}
}

[Test]
public async Task WorkerDirectPostToOwnerShouldRaiseMessage()
{
Expand Down Expand Up @@ -133,4 +163,4 @@ private static async Task WaitForResultAsync(AngleSharp.Dom.IDocument document,
}
}
}
}
}
45 changes: 31 additions & 14 deletions src/AngleSharp.Js/Dom/Worker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,45 @@ public Worker(IWindow window, String source)

var parentContext = _window.Document.Context;
_scripting = parentContext.GetService<JsScriptingService>() ?? throw new DomException(DomError.NotSupported);
_workerLoop = new JsEventLoop();
var workerConfig = Configuration.Default
.With(_scripting)
.WithOnly(_workerLoop);
_workerContext = BrowsingContext.New(workerConfig);

EnsureLoaderAvailable();
var workerUrl = ResolveUrl(source);
_pendingMessages = new Queue<Object>();

EnsureLoaderAvailable();
// Check the parent's capabilities before starting a thread that a failed constructor
// would leave unreachable. Release it if context creation or scheduling fails too.
_workerLoop = new JsEventLoop();
try
{
var workerConfig = Configuration.Default
.With(_scripting)
.WithOnly(_workerLoop);
_workerContext = BrowsingContext.New(workerConfig);

var workerUrl = ResolveUrl(source);
Enqueue(_workerLoop, TaskPriority.Critical, () =>
Enqueue(_workerLoop, TaskPriority.Critical, () =>
{
try
{
InitializeWorker(workerUrl);
}
catch (Exception ex)
{
_startupError = ex;
}
});
}
catch
{
try
{
InitializeWorker(workerUrl);
_workerContext?.Dispose();
}
catch (Exception ex)
finally
{
_startupError = ex;
((IDisposable)_workerLoop).Dispose();
}
});

throw;
}
}

#endregion
Expand Down Expand Up @@ -289,4 +306,4 @@ private static void Enqueue(IEventLoop loop, TaskPriority priority, Action actio
#endregion

}
}
}
Loading