Skip to content

Commit

Permalink
Remove lifecycle things from servers
Browse files Browse the repository at this point in the history
  • Loading branch information
shmuelie committed Jan 26, 2025
1 parent b7c2e40 commit 0e41cbf
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 240 deletions.
136 changes: 11 additions & 125 deletions src/Shmuelie.WinRTServer/ComServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Runtime.InteropServices.Marshalling;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using System.Timers;
using Shmuelie.WinRTServer.Internal;
using Shmuelie.WinRTServer.Internal.Windows;
using Windows.Win32.Foundation;
Expand All @@ -16,40 +15,17 @@ namespace Shmuelie.WinRTServer;
/// <summary>
/// An Out of Process COM Server.
/// </summary>
/// <remarks>
/// <para>Allows for types to be created using COM activation instead of WinRT activation like <see cref="WinRtServer"/>.</para>
/// <para>Typical usage is to call from an <see langword="await"/> <see langword="using"/> block, using <see cref="WaitForFirstObjectAsync"/> to not close until it is safe to do so.</para>
/// <code language="cs">
/// <![CDATA[
/// await using (ComServer server = new ComServer())
/// {
/// server.RegisterClass<RemoteThing, IRemoteThing>();
/// server.Start();
/// await server.WaitForFirstObjectAsync();
/// }
/// ]]>
/// </code>
/// </remarks>
/// <see cref="IAsyncDisposable"/>
/// <remarks>Allows for types to be created using COM activation instead of WinRT activation like <see cref="WinRtServer"/>.</remarks>
/// <see cref="IServer"/>
/// <threadsafety static="true" instance="false"/>
[SupportedOSPlatform("windows6.0.6000")]
public sealed class ComServer : IAsyncDisposable
public sealed class ComServer : IServer
{
/// <summary>
/// Map of class factories and the registration cookie from the CLSID that the factory creates.
/// </summary>
private readonly Dictionary<Guid, (BaseClassFactory factory, uint cookie)> factories = [];

/// <summary>
/// Collection of created instances.
/// </summary>
private readonly LinkedList<WeakReference> liveServers = new();

/// <summary>
/// Timer that checks if all created instances have been collected.
/// </summary>
private readonly Timer lifetimeCheckTimer;

private readonly StrategyBasedComWrappers comWrappers = new();

/// <summary>
Expand All @@ -69,47 +45,6 @@ public unsafe ComServer()
{
options.Get()->Set(GLOBALOPT_PROPERTIES.COMGLB_RO_SETTINGS, (nuint)GLOBALOPT_RO_FLAGS.COMGLB_FAST_RUNDOWN);
}

lifetimeCheckTimer = new()
{
Interval = 60000,
};
lifetimeCheckTimer.Elapsed += LifetimeCheckTimer_Elapsed;
}

/// <summary>
/// Handles <see cref="Timer.Elapsed"/> event from <see cref="lifetimeCheckTimer"/>.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An <see cref="ElapsedEventArgs"/> object that contains the event data.</param>
private void LifetimeCheckTimer_Elapsed(object? sender, ElapsedEventArgs e)
{
if (IsDisposed)
{
return;
}

uint? instanceCount = null;
GC.Collect();
for (LinkedListNode<WeakReference>? node = liveServers.First; node != null; node = node.Next)
{
if (!node.Value.IsAlive)
{
instanceCount = CoReleaseServerProcess();
var previous = node.Previous;
liveServers.Remove(node);
if (previous is null)
{
break;
}
node = previous;
}
}

if (instanceCount == 0)
{
Empty?.Invoke(this, EventArgs.Empty);
}
}

/// <summary>
Expand All @@ -128,7 +63,7 @@ public unsafe bool RegisterClassFactory(BaseClassFactory factory, ComWrappers co
ObjectDisposedException.ThrowIf(IsDisposed, this);
ArgumentNullException.ThrowIfNull(factory);
ArgumentNullException.ThrowIfNull(comWrappers);
if (lifetimeCheckTimer.Enabled)
if (IsRunning)
{
throw new InvalidOperationException("Can only add class factories when server is not running.");
}
Expand Down Expand Up @@ -162,7 +97,7 @@ public unsafe bool RegisterClassFactory(BaseClassFactory factory, ComWrappers co
public unsafe bool UnregisterClassFactory(Guid clsid)
{
ObjectDisposedException.ThrowIf(IsDisposed, this);
if (lifetimeCheckTimer.Enabled)
if (IsRunning)
{
throw new InvalidOperationException("Can only remove class factories when server is not running.");
}
Expand All @@ -186,15 +121,14 @@ private void Factory_InstanceCreated(object? sender, InstanceCreatedEventArgs e)
return;
}

liveServers.AddLast(new WeakReference(e.Instance));
InstanceCreated?.Invoke(this, e);
firstInstanceCreated?.TrySetResult(e.Instance);
}

/// <summary>
/// Gets a value indicating whether the server is running.
/// </summary>
public bool IsRunning => lifetimeCheckTimer.Enabled;
public bool IsRunning { get; private set; }

/// <summary>
/// Starts the server.
Expand All @@ -204,13 +138,13 @@ private void Factory_InstanceCreated(object? sender, InstanceCreatedEventArgs e)
public void Start()
{
ObjectDisposedException.ThrowIf(IsDisposed, this);
if (lifetimeCheckTimer.Enabled)
if (IsRunning)
{
return;
}

firstInstanceCreated = new();
lifetimeCheckTimer.Start();
IsRunning = true;
CoResumeClassObjects().ThrowOnFailure();
}

Expand All @@ -221,13 +155,13 @@ public void Start()
public void Stop()
{
ObjectDisposedException.ThrowIf(IsDisposed, this);
if (!lifetimeCheckTimer.Enabled)
if (!IsRunning)
{
return;
}

firstInstanceCreated = null;
lifetimeCheckTimer.Stop();
IsRunning = false;
CoSuspendClassObjects().ThrowOnFailure();
}

Expand Down Expand Up @@ -260,20 +194,14 @@ public bool IsDisposed
/// <summary>
/// Force the server to stop and release all resources.
/// </summary>
/// <remarks>Unlike <see cref="DisposeAsync"/>, <see cref="UnsafeDispose"/> will ignore if any objects are still alive before unregistering class factories.</remarks>
/// <seealso cref="DisposeAsync"/>
public void UnsafeDispose()
public void Dispose()
{
if (!IsDisposed)
{
try
{
_ = CoSuspendClassObjects();

liveServers.Clear();
lifetimeCheckTimer.Stop();
lifetimeCheckTimer.Dispose();

foreach (var clsid in factories.Keys)
{
_ = UnregisterClassFactory(clsid);
Expand All @@ -286,48 +214,6 @@ public void UnsafeDispose()
}
}

/// <inheritdoc/>
public async ValueTask DisposeAsync()
{
if (!IsDisposed)
{
try
{
_ = CoSuspendClassObjects();

if (liveServers.Count != 0)
{
TaskCompletionSource<bool> tcs = new();
void Ended(object? sender, EventArgs e)
{
tcs.SetResult(true);
}

Empty += Ended;
await tcs.Task.ConfigureAwait(false);
Empty -= Ended;
}

lifetimeCheckTimer.Stop();
lifetimeCheckTimer.Dispose();

foreach (var clsid in factories.Keys)
{
_ = UnregisterClassFactory(clsid);
}
}
finally
{
IsDisposed = true;
}
}
}

/// <summary>
/// Occurs when the server has no live objects.
/// </summary>
public event EventHandler? Empty;

/// <summary>
/// Occurs when the server creates an object.
/// </summary>
Expand Down
Loading

0 comments on commit 0e41cbf

Please sign in to comment.