Skip to content

Commit

Permalink
refactor init to not be global (#336)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
wjwwood authored Nov 30, 2018
1 parent dfaa412 commit 97ad001
Show file tree
Hide file tree
Showing 55 changed files with 1,950 additions and 628 deletions.
4 changes: 3 additions & 1 deletion rcl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ set(${PROJECT_NAME}_sources
src/rcl/arguments.c
src/rcl/client.c
src/rcl/common.c
src/rcl/context.c
src/rcl/expand_topic_name.c
src/rcl/graph.c
src/rcl/guard_condition.c
src/rcl/init.c
src/rcl/init_options.c
src/rcl/lexer.c
src/rcl/lexer_lookahead.c
src/rcl/node.c
src/rcl/publisher.c
src/rcl/rcl.c
src/rcl/remap.c
src/rcl/rmw_implementation_identifier_check.c
src/rcl/service.c
Expand Down
24 changes: 0 additions & 24 deletions rcl/include/rcl/arguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,30 +278,6 @@ rcl_ret_t
rcl_arguments_fini(
rcl_arguments_t * args);

/// Get a global instance of command line arguments.
/**
* \sa rcl_init(int, char **, rcl_allocator_t)
* \sa rcl_shutdown()
* This returns parsed command line arguments that were passed to `rcl_init()`.
* The value returned by this function is undefined before `rcl_init()` is called and after
* `rcl_shutdown()` is called.
* The return value must not be finalized.
*
* <hr>
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | No
* Thread-Safe | Yes
* Uses Atomics | No
* Lock-Free | Yes
*
* \return a global instance of parsed command line arguments.
*/
RCL_PUBLIC
RCL_WARN_UNUSED
rcl_arguments_t *
rcl_get_global_arguments();

#ifdef __cplusplus
}
#endif
Expand Down
261 changes: 261 additions & 0 deletions rcl/include/rcl/context.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
// Copyright 2018 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef RCL__CONTEXT_H_
#define RCL__CONTEXT_H_

#ifdef __cplusplus
extern "C"
{
#endif

#include "rmw/init.h"

#include "rcl/allocator.h"
#include "rcl/arguments.h"
#include "rcl/init_options.h"
#include "rcl/macros.h"
#include "rcl/types.h"
#include "rcl/visibility_control.h"

typedef uint64_t rcl_context_instance_id_t;

struct rcl_context_impl_t;

/// Encapsulates the non-global state of an init/shutdown cycle.
/**
* The context is used in the creation of top level entities like nodes and
* guard conditions, as well as to shutdown a specific instance of init.
*
* Here is a diagram of a typical context's lifecycle:
*
* ```
* +---------------+
* | |
* +--> uninitialized +---> rcl_get_zero_initialized_context() +
* | | | |
* | +---------------+ |
* | |
* | +-----------------------------------------------+
* | |
* | +--------v---------+ +-----------------------+
* | | | | |
* | | zero-initialized +-> rcl_init() +-> initialized and valid +-> rcl_shutdown() +
* | | | | | |
* | +------------------+ +-----------------------+ |
* | |
* | +-----------------------------------------------------------------+
* | |
* | +------------v------------+
* | | |
* | | initialized but invalid +---> finalize all entities, then rcl_context_fini() +
* | | | |
* | +-------------------------+ |
* | |
* +---------------------------------------------------------------------------------+
* ```
*
* A declared but not defined `rcl_context_t` instance is considered to be
* "uninitialized", and passing an uninitialized context to any functions will
* result in undefined behavior.
* Some functions, like `rcl_init()` require the context instance to be
* zero initialized (all members set to "zero" state) before use.
*
* Zero initialization of an `rcl_context_t` should be done with
* `rcl_get_zero_initialized_context()`, which ensures the context is in a safe
* state for initialization with `rcl_init()`.
*
* Initialization of an `rcl_context_t` should be done with `rcl_init()`, after
* which the context is considered both initialized and valid.
* After initialization it can be used in the creation of other entities like
* nodes and guard conditions.
*
* At any time the context can be invalidated by calling `rcl_shutdown()` on
* the `rcl_context_t`, after which the context is still initialized but now
* invalid.
*
* Invalidation indicates to other entities that the context was shutdown, but
* is still accessible for use during cleanup of themselves.
*
* After being invalidated, and after all of the entities which used it have
* been finalized, the context should be finalized with `rcl_context_fini()`.
*
* Finalizing the context while entities which have copies of it have not yet
* been finalized is undefined behavior.
* Therefore, the context's lifetime (between calls to `rcl_init()` and
* `rcl_context_fini()`) should exceed the lifetime of all entities which use
* it directly (e.g. nodes and guard conditions) or indirectly (e.g.
* subscriptions and topics).
*/
typedef struct rcl_context_t
{
/// Global arguments for all nodes which share this context.
/** Typically generated by the parsing of argc/argv in `rcl_init()`. */
rcl_arguments_t global_arguments;

/// Implementation specific pointer.
struct rcl_context_impl_t * impl;

// The assumption that this is big enough for an atomic_uint_least64_t is
// ensured with a static_assert in the context.c file.
// In most cases it should just be a plain uint64_t.
#if !defined(RCL_CONTEXT_ATOMIC_INSTANCE_ID_STORAGE_SIZE)
#define RCL_CONTEXT_ATOMIC_INSTANCE_ID_STORAGE_SIZE sizeof(uint_least64_t)
#endif
/// Private storage for instance ID atomic.
/**
* Accessing the instance id should be done using the function
* `rcl_context_get_instance_id()` because the instance id's type is an
* atomic and needs to be accessed properly to ensure safety.
*
* The instance id should not be changed manually - doing so is undefined
* behavior.
*
* The instance id cannot be protected within the `impl` pointer's type
* because it needs to be accessible even when the context is zero
* initialized and therefore `impl` is `NULL`.
* Specifically, storing the instance id in the `impl` would introduce a
* race condition between accessing it and finalizing the context.
* Additionally, C11 atomics (i.e. "stdatomic.h") cannot be used directly
* here in the case that this header is included into a C++ program.
* See this paper for an effort to make this possible in the future:
* http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0943r1.html
*/
uint8_t instance_id_storage[RCL_CONTEXT_ATOMIC_INSTANCE_ID_STORAGE_SIZE];
} rcl_context_t;

/// Return a zero initialization context object.
RCL_PUBLIC
RCL_WARN_UNUSED
rcl_context_t
rcl_get_zero_initialized_context(void);

// See `rcl_init()` for initialization of the context.

/// Finalize a context.
/**
* The context to be finalized must have been previously initialized with
* `rcl_init()`, and then later invalidated with `rcl_shutdown()`.
* If context is `NULL`, then `RCL_RET_INVALID_ARGUMENT` is returned.
* If context is zero-initialized, then `RCL_RET_INVALID_ARGUMENT` is returned.
* If context is initialized and valid (`rcl_shutdown()` was not called on it),
* then `RCL_RET_INVALID_ARGUMENT` is returned.
*
* <hr>
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | Yes
* Thread-Safe | No
* Uses Atomics | Yes
* Lock-Free | Yes [1]
* <i>[1] if `atomic_is_lock_free()` returns true for `atomic_uint_least64_t`</i>
*
* \return `RCL_RET_OK` if the shutdown was completed successfully, or
* \return `RCL_RET_INVALID_ARGUMENT` if any arguments are invalid, or
* \return `RCL_RET_ERROR` if an unspecified error occur.
*/
RCL_PUBLIC
RCL_WARN_UNUSED
rcl_ret_t
rcl_context_fini(rcl_context_t * context);

// See `rcl_shutdown()` for invalidation of the context.

/// Return the init options used during initialization for this context.
/**
* This function can fail and return `NULL` if:
* - context is NULL
* - context is zero-initialized, e.g. context->impl is `NULL`
*
* If context is uninitialized then that is undefined behavior.
*
* If `NULL` is returned an error message will have been set.
*
* The options are for reference only, and therefore the returned pointer is
* const.
* Changing the values in the options is undefined behavior but will likely
* have no effect.
*
* <hr>
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | No
* Thread-Safe | Yes
* Uses Atomics | Yes
* Lock-Free | Yes
*
* \param[in] context object from which the init options should be retrieved
* \return pointer to the the init options, or
* \return `NULL` if there was an error
*/
RCL_PUBLIC
RCL_WARN_UNUSED
const rcl_init_options_t *
rcl_context_get_init_options(rcl_context_t * context);

/// Returns an unsigned integer that is unique to the given context, or `0` if invalid.
/**
* The given context must be non-`NULL`, but does not need to be initialized or valid.
* If context is `NULL`, then `0` will be returned.
* If context is uninitialized, then it is undefined behavior.
*
* The instance ID may be `0` if the context is zero-initialized or if the
* context has been invalidated by `rcl_shutdown()`.
*
* <hr>
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | No
* Thread-Safe | Yes
* Uses Atomics | Yes
* Lock-Free | Yes [1]
* <i>[1] if `atomic_is_lock_free()` returns true for `atomic_uint_least64_t`</i>
*
* \param[in] context object from which the instance id should be retrieved
* \return a unique id specific to this context instance, or
* \return `0` if invalid, or
* \return `0` if context is `NULL`
*/
RCL_PUBLIC
RCL_WARN_UNUSED
rcl_context_instance_id_t
rcl_context_get_instance_id(rcl_context_t * context);

/// Return `true` if the given context is currently valid, otherwise `false`.
/**
* If context is `NULL`, then `false` is returned.
* If context is zero-initialized, then `false` is returned.
* If context is uninitialized, then it is undefined behavior.
*
* Attribute | Adherence
* ------------------ | -------------
* Allocates Memory | No
* Thread-Safe | Yes
* Uses Atomics | Yes
* Lock-Free | Yes [1]
* <i>[1] if `atomic_is_lock_free()` returns true for `atomic_uint_least64_t`</i>
*
* \param[in] context object which should be checked for validity
* \return `true` if valid, otherwise `false`
*/
RCL_PUBLIC
RCL_WARN_UNUSED
bool
rcl_context_is_valid(rcl_context_t * context);

#ifdef __cplusplus
}
#endif

#endif // RCL__CONTEXT_H_
15 changes: 14 additions & 1 deletion rcl/include/rcl/guard_condition.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ extern "C"
#endif

#include "rcl/allocator.h"
#include "rcl/context.h"
#include "rcl/macros.h"
#include "rcl/types.h"
#include "rcl/visibility_control.h"
Expand All @@ -31,6 +32,9 @@ struct rcl_guard_condition_impl_t;
/// Handle for a rcl guard condition.
typedef struct rcl_guard_condition_t
{
/// Context associated with this guard condition.
rcl_context_t * context;

struct rcl_guard_condition_impl_t * impl;
} rcl_guard_condition_t;

Expand Down Expand Up @@ -61,7 +65,7 @@ rcl_get_zero_initialized_guard_condition(void);
* rcl_guard_condition_t guard_condition = rcl_get_zero_initialized_guard_condition();
* // ... customize guard condition options
* rcl_ret_t ret = rcl_guard_condition_init(
* &guard_condition, rcl_guard_condition_get_default_options());
* &guard_condition, context, rcl_guard_condition_get_default_options());
* // ... error handling, and on shutdown do deinitialization:
* ret = rcl_guard_condition_fini(&guard_condition);
* // ... error handling for rcl_guard_condition_fini()
Expand All @@ -76,9 +80,12 @@ rcl_get_zero_initialized_guard_condition(void);
* Lock-Free | Yes
*
* \param[inout] guard_condition preallocated guard_condition structure
* \param[in] context the context instance with which the guard condition
* should be associated
* \param[in] options the guard_condition's options
* \return `RCL_RET_OK` if guard_condition was initialized successfully, or
* \return `RCL_RET_ALREADY_INIT` if the guard condition is already initialized, or
* \return `RCL_RET_NOT_INIT` if the given context is invalid, or
* \return `RCL_RET_INVALID_ARGUMENT` if any arguments are invalid, or
* \return `RCL_RET_BAD_ALLOC` if allocating memory failed, or
* \return `RCL_RET_ERROR` if an unspecified error occurs.
Expand All @@ -88,6 +95,7 @@ RCL_WARN_UNUSED
rcl_ret_t
rcl_guard_condition_init(
rcl_guard_condition_t * guard_condition,
rcl_context_t * context,
const rcl_guard_condition_options_t options);

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

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

0 comments on commit 97ad001

Please sign in to comment.