Skip to content

Commit d004d9e

Browse files
committed
dispose guard conditions after invoking IContext.OnShutdown
This prevents violating the documentation.
1 parent da31e2e commit d004d9e

File tree

4 files changed

+70
-10
lines changed

4 files changed

+70
-10
lines changed

src/ros2cs/ros2cs_core/Context.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ public sealed class Context : IContext
5252
/// </remarks>
5353
private Dictionary<string, Node> ROSNodes = new Dictionary<string, Node>();
5454

55+
/// <summary>
56+
/// Collection of guard conditions active in this context.
57+
/// </summary>
58+
/// <remarks>
59+
/// Also used for synchronisation when creating / removing guard conditions.
60+
/// </remarks>
61+
private HashSet<GuardCondition> GuardConditions = new HashSet<GuardCondition>();
62+
5563
/// <summary>
5664
/// Get the current RMW implementation.
5765
/// </summary>
@@ -128,6 +136,41 @@ internal bool RemoveNode(string name)
128136
}
129137
}
130138

139+
/// <summary>
140+
/// Create a guard condition.
141+
/// </summary>
142+
/// <remarks>
143+
/// This method is thread safe.
144+
/// </remarks>
145+
/// <param name="callback"> Callback executed by the executor when the guard condition is triggered. </param>
146+
/// <returns> A new guard condition instance. </returns>
147+
internal GuardCondition CreateGuardCondition(Action callback)
148+
{
149+
lock (this.GuardConditions)
150+
{
151+
GuardCondition guardCondition = new GuardCondition(this, callback);
152+
this.GuardConditions.Add(guardCondition);
153+
return guardCondition;
154+
}
155+
}
156+
157+
/// <summary>
158+
/// Remove a guard condition.
159+
/// </summary>
160+
/// <remarks>
161+
/// This method is intended to be used by <see cref="GuardCondition.Dispose"/> and does not dispose the guard condition.
162+
/// Furthermore, it is thread safe.
163+
/// </remarks>
164+
/// <param name="guardCondition"> Guard condition to remove. </param>
165+
/// <returns> If the guard condition existed in this context and has been removed. </returns>
166+
internal bool RemoveGuardCondition(GuardCondition guardCondition)
167+
{
168+
lock (this.GuardConditions)
169+
{
170+
return this.GuardConditions.Remove(guardCondition);
171+
}
172+
}
173+
131174
/// <remarks>
132175
/// This method is not thread safe.
133176
/// Do not call while the context or any entities
@@ -154,7 +197,7 @@ private void Dispose(bool disposing)
154197
{
155198
Utils.CheckReturnEnum(ret);
156199
}
157-
// only continue if ROSNodes has not been finalized
200+
// only continue if ROSNodes and GuardConditions has not been finalized
158201
if (disposing)
159202
{
160203
this.OnShutdown?.Invoke();
@@ -163,6 +206,11 @@ private void Dispose(bool disposing)
163206
node.DisposeFromContext();
164207
}
165208
this.ROSNodes.Clear();
209+
foreach (var guardCondition in this.GuardConditions)
210+
{
211+
guardCondition.DisposeFromContext();
212+
}
213+
this.GuardConditions.Clear();
166214
// only safe when all nodes are gone, not calling Dispose() will leak the Handle
167215
Utils.CheckReturnEnum(NativeRcl.rcl_context_fini(this.Handle));
168216
this.FreeHandles();

src/ros2cs/ros2cs_core/GuardCondition.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
using System;
16+
using System.Diagnostics;
1617

1718
namespace ROS2
1819
{
@@ -68,7 +69,6 @@ out IntPtr handle
6869
}
6970
Utils.CheckReturnEnum(ret);
7071
this.Handle = handle;
71-
context.OnShutdown += this.Dispose;
7272
}
7373

7474
/// <summary>
@@ -123,13 +123,26 @@ private void Dispose(bool disposing)
123123
return;
124124
}
125125

126-
Utils.CheckReturnEnum(NativeRcl.rcl_guard_condition_fini(this.Handle));
127-
this.FreeHandles();
128-
126+
// only do if Context.GuardConditions has not been finalized
129127
if (disposing)
130128
{
131-
this.Context.OnShutdown -= this.Dispose;
129+
bool success = this.Context.RemoveGuardCondition(this);
130+
Debug.Assert(success, message: "failed to remove guard condition");
131+
}
132+
133+
this.DisposeFromContext();
134+
}
135+
136+
/// <summary> Dispose without modifying the context. </summary>
137+
internal void DisposeFromContext()
138+
{
139+
if (this.Handle == IntPtr.Zero)
140+
{
141+
return;
132142
}
143+
144+
Utils.CheckReturnEnum(NativeRcl.rcl_guard_condition_fini(this.Handle));
145+
this.FreeHandles();
133146
}
134147

135148
/// <summary>

src/ros2cs/ros2cs_core/executors/ManualExecutor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public bool IsReadOnly
115115
/// <exception cref="ObjectDisposedException"> If <paramref name="context"/> is disposed. </exception>
116116
public ManualExecutor(Context context) : this(
117117
new WaitSet(context),
118-
new GuardCondition(context, () => { })
118+
context.CreateGuardCondition(() => { })
119119
)
120120
{ }
121121

src/ros2cs/ros2cs_tests/src/GuardConditionTest.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ public class GuardConditionTest
2929
public void SetUp()
3030
{
3131
this.Context = new Context();
32-
this.GuardCondition = new GuardCondition(
33-
this.Context,
32+
this.GuardCondition = this.Context.CreateGuardCondition(
3433
() => { throw new InvalidOperationException("guard condition was called"); }
3534
);
3635
}
@@ -66,7 +65,7 @@ public void DisposedContextHandling()
6665
{
6766
this.Context.Dispose();
6867

69-
Assert.Throws<ObjectDisposedException>(() => new GuardCondition(this.Context, () => {}));
68+
Assert.Throws<ObjectDisposedException>(() => this.Context.CreateGuardCondition(() => { }));
7069
}
7170

7271
[Test]

0 commit comments

Comments
 (0)