Skip to content

Commit 78e8552

Browse files
committed
dispose wait set after invoking IContext.OnShutdown
This prevents violating the documentation.
1 parent af0b8dc commit 78e8552

File tree

4 files changed

+93
-12
lines changed

4 files changed

+93
-12
lines changed

src/ros2cs/ros2cs_core/Context.cs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ public sealed class Context : IContext
6060
/// </remarks>
6161
private HashSet<GuardCondition> GuardConditions = new HashSet<GuardCondition>();
6262

63+
/// <summary>
64+
/// Collection of wait sets active in this context;
65+
/// </summary>
66+
/// <remarks>
67+
/// Also used for synchronisation when creating / removing guard conditions.
68+
/// </remarks>
69+
private HashSet<WaitSet> WaitSets = new HashSet<WaitSet>();
70+
6371
/// <summary>
6472
/// Get the current RMW implementation.
6573
/// </summary>
@@ -171,6 +179,40 @@ internal bool RemoveGuardCondition(GuardCondition guardCondition)
171179
}
172180
}
173181

182+
/// <summary>
183+
/// Create a wait set.
184+
/// </summary>
185+
/// <remarks>
186+
/// This method is thread safe.
187+
/// </remarks>
188+
/// <returns> A new wait set instance. </returns>
189+
internal WaitSet CreateWaitSet()
190+
{
191+
lock (this.WaitSets)
192+
{
193+
WaitSet waitSet = new WaitSet(this);
194+
this.WaitSets.Add(waitSet);
195+
return waitSet;
196+
}
197+
}
198+
199+
/// <summary>
200+
/// Remove a wait set.
201+
/// </summary>
202+
/// <remarks>
203+
/// This method is intended to be used by <see cref="WaitSet.Dispose"/> and does not dispose the wait set.
204+
/// Furthermore, it is thread safe.
205+
/// </remarks>
206+
/// <param name="waitSet"> Wait set to remove. </param>
207+
/// <returns> If the wait set existed in this context and has been removed. </returns>
208+
internal bool RemoveWaitSet(WaitSet waitSet)
209+
{
210+
lock (this.WaitSets)
211+
{
212+
return this.WaitSets.Remove(waitSet);
213+
}
214+
}
215+
174216
/// <remarks>
175217
/// This method is not thread safe.
176218
/// Do not call while the context or any entities
@@ -197,7 +239,7 @@ private void Dispose(bool disposing)
197239
{
198240
Utils.CheckReturnEnum(ret);
199241
}
200-
// only continue if ROSNodes and GuardConditions has not been finalized
242+
// only continue if the collections of the active primitives have not been finalized
201243
if (disposing)
202244
{
203245
this.OnShutdown?.Invoke();
@@ -211,7 +253,12 @@ private void Dispose(bool disposing)
211253
guardCondition.DisposeFromContext();
212254
}
213255
this.GuardConditions.Clear();
214-
// only safe when all nodes are gone, not calling Dispose() will leak the Handle
256+
foreach (var waitSet in this.WaitSets)
257+
{
258+
waitSet.DisposeFromContext();
259+
}
260+
this.WaitSets.Clear();
261+
// only safe when all primitives are gone, not calling Dispose() will leak the Handle
215262
Utils.CheckReturnEnum(NativeRcl.rcl_context_fini(this.Handle));
216263
this.FreeHandles();
217264
}

src/ros2cs/ros2cs_core/WaitSet.cs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using System;
1717
using System.Collections;
1818
using System.Collections.Generic;
19+
using System.Diagnostics;
1920
using System.Linq;
2021

2122
namespace ROS2
@@ -49,7 +50,12 @@ internal sealed class WaitSet : IReadOnlyCollection<IWaitable>, IExtendedDisposa
4950
/// <summary>
5051
/// Context associated with this wait set.
5152
/// </summary>
52-
public IContext Context { get; private set; }
53+
public IContext Context
54+
{
55+
get => this.ROSContext;
56+
}
57+
58+
private Context ROSContext;
5359

5460
/// <inheritdoc />
5561
public bool IsDisposed
@@ -99,7 +105,7 @@ public int Count
99105
/// <param name="context">Associated context</param>
100106
internal WaitSet(Context context)
101107
{
102-
this.Context = context;
108+
this.ROSContext = context;
103109
this.Handle = NativeRclInterface.rclcs_get_zero_initialized_wait_set();
104110
int ret = NativeRcl.rcl_wait_set_init(
105111
this.Handle,
@@ -117,7 +123,6 @@ internal WaitSet(Context context)
117123
this.FreeHandles();
118124
Utils.CheckReturnEnum(ret);
119125
}
120-
context.OnShutdown += this.Dispose;
121126
}
122127

123128
/// <inheritdoc />
@@ -314,16 +319,37 @@ private void Dispose(bool disposing)
314319
return;
315320
}
316321

322+
if (disposing)
323+
{
324+
bool success = this.ROSContext.RemoveWaitSet(this);
325+
Debug.Assert(success, "failed to remove wait set");
326+
this.ClearCollections();
327+
}
328+
317329
Utils.CheckReturnEnum(NativeRcl.rcl_wait_set_fini(this.Handle));
318330
this.FreeHandles();
331+
}
319332

320-
if (disposing)
333+
/// <summary> Dispose without modifying the context. </summary>
334+
internal void DisposeFromContext()
335+
{
336+
if (this.Handle == IntPtr.Zero)
321337
{
322-
this.Context.OnShutdown -= this.Dispose;
323-
this.Subscriptions.Clear();
324-
this.Clients.Clear();
325-
this.Services.Clear();
338+
return;
326339
}
340+
341+
this.ClearCollections();
342+
343+
Utils.CheckReturnEnum(NativeRcl.rcl_wait_set_fini(this.Handle));
344+
this.FreeHandles();
345+
}
346+
347+
private void ClearCollections()
348+
{
349+
this.Subscriptions.Clear();
350+
this.Clients.Clear();
351+
this.Services.Clear();
352+
this.GuardConditions.Clear();
327353
}
328354

329355
private void FreeHandles()

src/ros2cs/ros2cs_core/executors/ManualExecutor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public bool IsReadOnly
114114
/// <param name="context"> Context to associate with. </param>
115115
/// <exception cref="ObjectDisposedException"> If <paramref name="context"/> is disposed. </exception>
116116
public ManualExecutor(Context context) : this(
117-
new WaitSet(context),
117+
context.CreateWaitSet(),
118118
context.CreateGuardCondition(() => { })
119119
)
120120
{ }

src/ros2cs/ros2cs_tests/src/WaitSetTest.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public class WaitSetTest
3636
public void SetUp()
3737
{
3838
this.Context = new Context();
39-
this.WaitSet = new WaitSet(this.Context);
39+
this.WaitSet = this.Context.CreateWaitSet();
4040
}
4141

4242
[TearDown]
@@ -65,6 +65,14 @@ public void DoubleDisposeWaitSet()
6565
Assert.That(this.WaitSet.IsDisposed, Is.True);
6666
}
6767

68+
[Test]
69+
public void OnShutdownDisposal()
70+
{
71+
this.Context.OnShutdown += () => Assert.That(this.WaitSet.IsDisposed, Is.False);
72+
73+
this.Context.Dispose();
74+
}
75+
6876
[Test]
6977
public void TestSubscriptionCollection()
7078
{

0 commit comments

Comments
 (0)