Skip to content

Commit 97ad001

Browse files
authored
refactor init to not be global (#336)
* refactor init to not be global Signed-off-by: William Woodall <[email protected]> * style changes Signed-off-by: William Woodall <[email protected]> * refactor to hide use of C11 atomics in implementation Signed-off-by: William Woodall <[email protected]> * fix new action tests Signed-off-by: William Woodall <[email protected]> * use alternative atomic init for Windows support * updates after rebase Signed-off-by: William Woodall <[email protected]> * cleanup rmw_init_options before copying Signed-off-by: William Woodall <[email protected]> * fix two bugs in new init code * relax validity checks in a few places to facilitate post shutdown cleanup Signed-off-by: William Woodall <[email protected]> * fixing tests for new API behavior Signed-off-by: William Woodall <[email protected]> * to allocator -> to allocate * acutally call rmw_shutdown() and address review comments Signed-off-by: William Woodall <[email protected]>
1 parent dfaa412 commit 97ad001

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1950
-628
lines changed

rcl/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@ set(${PROJECT_NAME}_sources
3030
src/rcl/arguments.c
3131
src/rcl/client.c
3232
src/rcl/common.c
33+
src/rcl/context.c
3334
src/rcl/expand_topic_name.c
3435
src/rcl/graph.c
3536
src/rcl/guard_condition.c
37+
src/rcl/init.c
38+
src/rcl/init_options.c
3639
src/rcl/lexer.c
3740
src/rcl/lexer_lookahead.c
3841
src/rcl/node.c
3942
src/rcl/publisher.c
40-
src/rcl/rcl.c
4143
src/rcl/remap.c
4244
src/rcl/rmw_implementation_identifier_check.c
4345
src/rcl/service.c

rcl/include/rcl/arguments.h

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -278,30 +278,6 @@ rcl_ret_t
278278
rcl_arguments_fini(
279279
rcl_arguments_t * args);
280280

281-
/// Get a global instance of command line arguments.
282-
/**
283-
* \sa rcl_init(int, char **, rcl_allocator_t)
284-
* \sa rcl_shutdown()
285-
* This returns parsed command line arguments that were passed to `rcl_init()`.
286-
* The value returned by this function is undefined before `rcl_init()` is called and after
287-
* `rcl_shutdown()` is called.
288-
* The return value must not be finalized.
289-
*
290-
* <hr>
291-
* Attribute | Adherence
292-
* ------------------ | -------------
293-
* Allocates Memory | No
294-
* Thread-Safe | Yes
295-
* Uses Atomics | No
296-
* Lock-Free | Yes
297-
*
298-
* \return a global instance of parsed command line arguments.
299-
*/
300-
RCL_PUBLIC
301-
RCL_WARN_UNUSED
302-
rcl_arguments_t *
303-
rcl_get_global_arguments();
304-
305281
#ifdef __cplusplus
306282
}
307283
#endif

rcl/include/rcl/context.h

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
// Copyright 2018 Open Source Robotics Foundation, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef RCL__CONTEXT_H_
16+
#define RCL__CONTEXT_H_
17+
18+
#ifdef __cplusplus
19+
extern "C"
20+
{
21+
#endif
22+
23+
#include "rmw/init.h"
24+
25+
#include "rcl/allocator.h"
26+
#include "rcl/arguments.h"
27+
#include "rcl/init_options.h"
28+
#include "rcl/macros.h"
29+
#include "rcl/types.h"
30+
#include "rcl/visibility_control.h"
31+
32+
typedef uint64_t rcl_context_instance_id_t;
33+
34+
struct rcl_context_impl_t;
35+
36+
/// Encapsulates the non-global state of an init/shutdown cycle.
37+
/**
38+
* The context is used in the creation of top level entities like nodes and
39+
* guard conditions, as well as to shutdown a specific instance of init.
40+
*
41+
* Here is a diagram of a typical context's lifecycle:
42+
*
43+
* ```
44+
* +---------------+
45+
* | |
46+
* +--> uninitialized +---> rcl_get_zero_initialized_context() +
47+
* | | | |
48+
* | +---------------+ |
49+
* | |
50+
* | +-----------------------------------------------+
51+
* | |
52+
* | +--------v---------+ +-----------------------+
53+
* | | | | |
54+
* | | zero-initialized +-> rcl_init() +-> initialized and valid +-> rcl_shutdown() +
55+
* | | | | | |
56+
* | +------------------+ +-----------------------+ |
57+
* | |
58+
* | +-----------------------------------------------------------------+
59+
* | |
60+
* | +------------v------------+
61+
* | | |
62+
* | | initialized but invalid +---> finalize all entities, then rcl_context_fini() +
63+
* | | | |
64+
* | +-------------------------+ |
65+
* | |
66+
* +---------------------------------------------------------------------------------+
67+
* ```
68+
*
69+
* A declared but not defined `rcl_context_t` instance is considered to be
70+
* "uninitialized", and passing an uninitialized context to any functions will
71+
* result in undefined behavior.
72+
* Some functions, like `rcl_init()` require the context instance to be
73+
* zero initialized (all members set to "zero" state) before use.
74+
*
75+
* Zero initialization of an `rcl_context_t` should be done with
76+
* `rcl_get_zero_initialized_context()`, which ensures the context is in a safe
77+
* state for initialization with `rcl_init()`.
78+
*
79+
* Initialization of an `rcl_context_t` should be done with `rcl_init()`, after
80+
* which the context is considered both initialized and valid.
81+
* After initialization it can be used in the creation of other entities like
82+
* nodes and guard conditions.
83+
*
84+
* At any time the context can be invalidated by calling `rcl_shutdown()` on
85+
* the `rcl_context_t`, after which the context is still initialized but now
86+
* invalid.
87+
*
88+
* Invalidation indicates to other entities that the context was shutdown, but
89+
* is still accessible for use during cleanup of themselves.
90+
*
91+
* After being invalidated, and after all of the entities which used it have
92+
* been finalized, the context should be finalized with `rcl_context_fini()`.
93+
*
94+
* Finalizing the context while entities which have copies of it have not yet
95+
* been finalized is undefined behavior.
96+
* Therefore, the context's lifetime (between calls to `rcl_init()` and
97+
* `rcl_context_fini()`) should exceed the lifetime of all entities which use
98+
* it directly (e.g. nodes and guard conditions) or indirectly (e.g.
99+
* subscriptions and topics).
100+
*/
101+
typedef struct rcl_context_t
102+
{
103+
/// Global arguments for all nodes which share this context.
104+
/** Typically generated by the parsing of argc/argv in `rcl_init()`. */
105+
rcl_arguments_t global_arguments;
106+
107+
/// Implementation specific pointer.
108+
struct rcl_context_impl_t * impl;
109+
110+
// The assumption that this is big enough for an atomic_uint_least64_t is
111+
// ensured with a static_assert in the context.c file.
112+
// In most cases it should just be a plain uint64_t.
113+
#if !defined(RCL_CONTEXT_ATOMIC_INSTANCE_ID_STORAGE_SIZE)
114+
#define RCL_CONTEXT_ATOMIC_INSTANCE_ID_STORAGE_SIZE sizeof(uint_least64_t)
115+
#endif
116+
/// Private storage for instance ID atomic.
117+
/**
118+
* Accessing the instance id should be done using the function
119+
* `rcl_context_get_instance_id()` because the instance id's type is an
120+
* atomic and needs to be accessed properly to ensure safety.
121+
*
122+
* The instance id should not be changed manually - doing so is undefined
123+
* behavior.
124+
*
125+
* The instance id cannot be protected within the `impl` pointer's type
126+
* because it needs to be accessible even when the context is zero
127+
* initialized and therefore `impl` is `NULL`.
128+
* Specifically, storing the instance id in the `impl` would introduce a
129+
* race condition between accessing it and finalizing the context.
130+
* Additionally, C11 atomics (i.e. "stdatomic.h") cannot be used directly
131+
* here in the case that this header is included into a C++ program.
132+
* See this paper for an effort to make this possible in the future:
133+
* http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0943r1.html
134+
*/
135+
uint8_t instance_id_storage[RCL_CONTEXT_ATOMIC_INSTANCE_ID_STORAGE_SIZE];
136+
} rcl_context_t;
137+
138+
/// Return a zero initialization context object.
139+
RCL_PUBLIC
140+
RCL_WARN_UNUSED
141+
rcl_context_t
142+
rcl_get_zero_initialized_context(void);
143+
144+
// See `rcl_init()` for initialization of the context.
145+
146+
/// Finalize a context.
147+
/**
148+
* The context to be finalized must have been previously initialized with
149+
* `rcl_init()`, and then later invalidated with `rcl_shutdown()`.
150+
* If context is `NULL`, then `RCL_RET_INVALID_ARGUMENT` is returned.
151+
* If context is zero-initialized, then `RCL_RET_INVALID_ARGUMENT` is returned.
152+
* If context is initialized and valid (`rcl_shutdown()` was not called on it),
153+
* then `RCL_RET_INVALID_ARGUMENT` is returned.
154+
*
155+
* <hr>
156+
* Attribute | Adherence
157+
* ------------------ | -------------
158+
* Allocates Memory | Yes
159+
* Thread-Safe | No
160+
* Uses Atomics | Yes
161+
* Lock-Free | Yes [1]
162+
* <i>[1] if `atomic_is_lock_free()` returns true for `atomic_uint_least64_t`</i>
163+
*
164+
* \return `RCL_RET_OK` if the shutdown was completed successfully, or
165+
* \return `RCL_RET_INVALID_ARGUMENT` if any arguments are invalid, or
166+
* \return `RCL_RET_ERROR` if an unspecified error occur.
167+
*/
168+
RCL_PUBLIC
169+
RCL_WARN_UNUSED
170+
rcl_ret_t
171+
rcl_context_fini(rcl_context_t * context);
172+
173+
// See `rcl_shutdown()` for invalidation of the context.
174+
175+
/// Return the init options used during initialization for this context.
176+
/**
177+
* This function can fail and return `NULL` if:
178+
* - context is NULL
179+
* - context is zero-initialized, e.g. context->impl is `NULL`
180+
*
181+
* If context is uninitialized then that is undefined behavior.
182+
*
183+
* If `NULL` is returned an error message will have been set.
184+
*
185+
* The options are for reference only, and therefore the returned pointer is
186+
* const.
187+
* Changing the values in the options is undefined behavior but will likely
188+
* have no effect.
189+
*
190+
* <hr>
191+
* Attribute | Adherence
192+
* ------------------ | -------------
193+
* Allocates Memory | No
194+
* Thread-Safe | Yes
195+
* Uses Atomics | Yes
196+
* Lock-Free | Yes
197+
*
198+
* \param[in] context object from which the init options should be retrieved
199+
* \return pointer to the the init options, or
200+
* \return `NULL` if there was an error
201+
*/
202+
RCL_PUBLIC
203+
RCL_WARN_UNUSED
204+
const rcl_init_options_t *
205+
rcl_context_get_init_options(rcl_context_t * context);
206+
207+
/// Returns an unsigned integer that is unique to the given context, or `0` if invalid.
208+
/**
209+
* The given context must be non-`NULL`, but does not need to be initialized or valid.
210+
* If context is `NULL`, then `0` will be returned.
211+
* If context is uninitialized, then it is undefined behavior.
212+
*
213+
* The instance ID may be `0` if the context is zero-initialized or if the
214+
* context has been invalidated by `rcl_shutdown()`.
215+
*
216+
* <hr>
217+
* Attribute | Adherence
218+
* ------------------ | -------------
219+
* Allocates Memory | No
220+
* Thread-Safe | Yes
221+
* Uses Atomics | Yes
222+
* Lock-Free | Yes [1]
223+
* <i>[1] if `atomic_is_lock_free()` returns true for `atomic_uint_least64_t`</i>
224+
*
225+
* \param[in] context object from which the instance id should be retrieved
226+
* \return a unique id specific to this context instance, or
227+
* \return `0` if invalid, or
228+
* \return `0` if context is `NULL`
229+
*/
230+
RCL_PUBLIC
231+
RCL_WARN_UNUSED
232+
rcl_context_instance_id_t
233+
rcl_context_get_instance_id(rcl_context_t * context);
234+
235+
/// Return `true` if the given context is currently valid, otherwise `false`.
236+
/**
237+
* If context is `NULL`, then `false` is returned.
238+
* If context is zero-initialized, then `false` is returned.
239+
* If context is uninitialized, then it is undefined behavior.
240+
*
241+
* Attribute | Adherence
242+
* ------------------ | -------------
243+
* Allocates Memory | No
244+
* Thread-Safe | Yes
245+
* Uses Atomics | Yes
246+
* Lock-Free | Yes [1]
247+
* <i>[1] if `atomic_is_lock_free()` returns true for `atomic_uint_least64_t`</i>
248+
*
249+
* \param[in] context object which should be checked for validity
250+
* \return `true` if valid, otherwise `false`
251+
*/
252+
RCL_PUBLIC
253+
RCL_WARN_UNUSED
254+
bool
255+
rcl_context_is_valid(rcl_context_t * context);
256+
257+
#ifdef __cplusplus
258+
}
259+
#endif
260+
261+
#endif // RCL__CONTEXT_H_

rcl/include/rcl/guard_condition.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ extern "C"
2121
#endif
2222

2323
#include "rcl/allocator.h"
24+
#include "rcl/context.h"
2425
#include "rcl/macros.h"
2526
#include "rcl/types.h"
2627
#include "rcl/visibility_control.h"
@@ -31,6 +32,9 @@ struct rcl_guard_condition_impl_t;
3132
/// Handle for a rcl guard condition.
3233
typedef struct rcl_guard_condition_t
3334
{
35+
/// Context associated with this guard condition.
36+
rcl_context_t * context;
37+
3438
struct rcl_guard_condition_impl_t * impl;
3539
} rcl_guard_condition_t;
3640

@@ -61,7 +65,7 @@ rcl_get_zero_initialized_guard_condition(void);
6165
* rcl_guard_condition_t guard_condition = rcl_get_zero_initialized_guard_condition();
6266
* // ... customize guard condition options
6367
* rcl_ret_t ret = rcl_guard_condition_init(
64-
* &guard_condition, rcl_guard_condition_get_default_options());
68+
* &guard_condition, context, rcl_guard_condition_get_default_options());
6569
* // ... error handling, and on shutdown do deinitialization:
6670
* ret = rcl_guard_condition_fini(&guard_condition);
6771
* // ... error handling for rcl_guard_condition_fini()
@@ -76,9 +80,12 @@ rcl_get_zero_initialized_guard_condition(void);
7680
* Lock-Free | Yes
7781
*
7882
* \param[inout] guard_condition preallocated guard_condition structure
83+
* \param[in] context the context instance with which the guard condition
84+
* should be associated
7985
* \param[in] options the guard_condition's options
8086
* \return `RCL_RET_OK` if guard_condition was initialized successfully, or
8187
* \return `RCL_RET_ALREADY_INIT` if the guard condition is already initialized, or
88+
* \return `RCL_RET_NOT_INIT` if the given context is invalid, or
8289
* \return `RCL_RET_INVALID_ARGUMENT` if any arguments are invalid, or
8390
* \return `RCL_RET_BAD_ALLOC` if allocating memory failed, or
8491
* \return `RCL_RET_ERROR` if an unspecified error occurs.
@@ -88,6 +95,7 @@ RCL_WARN_UNUSED
8895
rcl_ret_t
8996
rcl_guard_condition_init(
9097
rcl_guard_condition_t * guard_condition,
98+
rcl_context_t * context,
9199
const rcl_guard_condition_options_t options);
92100

93101
/// Same as rcl_guard_condition_init(), but reusing an existing rmw handle.
@@ -114,6 +122,9 @@ rcl_guard_condition_init(
114122
*
115123
* \param[inout] guard_condition preallocated guard_condition structure
116124
* \param[in] rmw_guard_condition existing rmw guard condition to reuse
125+
* \param[in] context the context instance with which the rmw guard condition
126+
* was initialized with, i.e. the rmw context inside rcl context needs to
127+
* match rmw context in rmw guard condition
117128
* \param[in] options the guard_condition's options
118129
* \return `RCL_RET_OK` if guard_condition was initialized successfully, or
119130
* \return `RCL_RET_ALREADY_INIT` if the guard condition is already initialized, or
@@ -125,6 +136,7 @@ rcl_ret_t
125136
rcl_guard_condition_init_from_rmw(
126137
rcl_guard_condition_t * guard_condition,
127138
const rmw_guard_condition_t * rmw_guard_condition,
139+
rcl_context_t * context,
128140
const rcl_guard_condition_options_t options);
129141

130142
/// Finalize a rcl_guard_condition_t.
@@ -142,6 +154,7 @@ rcl_guard_condition_init_from_rmw(
142154
* <i>[1] specifically not thread-safe with rcl_trigger_guard_condition()</i>
143155
*
144156
* \param[inout] guard_condition handle to the guard_condition to be finalized
157+
* \param[in] context the context originally used to init the guard condition
145158
* \return `RCL_RET_OK` if guard_condition was finalized successfully, or
146159
* \return `RCL_RET_INVALID_ARGUMENT` if any arguments are invalid, or
147160
* \return `RCL_RET_ERROR` if an unspecified error occurs.

0 commit comments

Comments
 (0)