Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor init to not be global #336

Merged
merged 12 commits into from
Nov 30, 2018
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() +
* | | | |
* | +-------------------------+ |
* | |
* +---------------------------------------------------------------------------------+
wjwwood marked this conversation as resolved.
Show resolved Hide resolved
* ```
*
* 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
wjwwood marked this conversation as resolved.
Show resolved Hide resolved
* 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