Skip to content

Commit 73718ca

Browse files
committed
feat: Thread configuration prototype
This is a prototype implementation of RCL for discussion about the thread configuration feature to receive and apply a set of scheduling parameters for the threads controlled by the ROS 2 executor. Our basic idea is as below. 1. Implement a new class rclcpp::thread and modify rclcpp to use it. This class has the same function set as the std::thread but also additional features to control its thread attributions. 2. Modify the rcl layer to receive a set of scheduling parameters. The parameters are described in YAML format and passed via command line parameters, environment variables, or files. 3. the rclcpp reads the parameters from rcl and applies them to each thread in the thread pool. There have been some discussions about this pull request, as below. [ROS Discourse] https://discourse.ros.org/t/adding-thread-attributes-configuration-in-ros-2-framework/30701 [ROS 2 Real-Time Working Group] ros-realtime/ros-realtime.github.io#18 Signed-off-by: Shoji Morita <[email protected]>
1 parent 1cb1209 commit 73718ca

File tree

19 files changed

+1095
-0
lines changed

19 files changed

+1095
-0
lines changed

rcl/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ set(${PROJECT_NAME}_sources
6767
src/rcl/service.c
6868
src/rcl/service_event_publisher.c
6969
src/rcl/subscription.c
70+
src/rcl/thread_attr.c
7071
src/rcl/time.c
7172
src/rcl/timer.c
7273
src/rcl/type_hash.c

rcl/include/rcl/arguments.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ typedef struct rcl_arguments_s
8383
/// logging (must be preceded with --enable- or --disable-).
8484
#define RCL_LOG_EXT_LIB_FLAG_SUFFIX "external-lib-logs"
8585

86+
/// The ROS flag that precedes the ROS thread attribute file path.
87+
#define RCL_THREAD_ATTRS_FILE_FLAG "--thread-attrs-file"
88+
89+
/// The ROS flag that precedes the ROS logging thread attribute.
90+
#define RCL_THREAD_ATTRS_VALUE_FLAG "--thread-attrs-value"
91+
8692
/// Return a rcl_arguments_t struct with members initialized to `NULL`.
8793
RCL_PUBLIC
8894
RCL_WARN_UNUSED
@@ -447,6 +453,24 @@ rcl_ret_t
447453
rcl_arguments_fini(
448454
rcl_arguments_t * args);
449455

456+
/// Return thread attribute parsed from the command line.
457+
/**
458+
* Thread attribute are parsed directly from command line arguments and
459+
* thread attribute files provided in the command line.
460+
*
461+
* \param[in] arguments An arguments structure that has been parsed.
462+
* \param[out] thread_attrs thread attribute as parsed from command line arguments.
463+
* This structure must be finalized by the caller.
464+
* \return #RCL_RET_OK if everything goes correctly, or
465+
* \return #RCL_RET_INVALID_ARGUMENT if any function arguments are invalid, or
466+
*/
467+
RCL_PUBLIC
468+
RCL_WARN_UNUSED
469+
rcl_ret_t
470+
rcl_arguments_get_thread_attrs(
471+
const rcl_arguments_t * arguments,
472+
rcl_thread_attrs_t ** thread_attrs);
473+
450474
#ifdef __cplusplus
451475
}
452476
#endif

rcl/include/rcl/context.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,23 @@ RCL_WARN_UNUSED
314314
rmw_context_t *
315315
rcl_context_get_rmw_context(rcl_context_t * context);
316316

317+
/// Returns the thread attribute context.
318+
/**
319+
* \param[in] context from which the thread attribute should be retrieved.
320+
* \param[out] thread_attrs output variable where the thread attribute will be returned.
321+
* \return #RCL_RET_INVALID_ARGUMENT if `context` is invalid (see rcl_context_is_valid()), or
322+
* \return #RCL_RET_INVALID_ARGUMENT if `context->impl` is `NULL`, or
323+
* \return #RCL_RET_INVALID_ARGUMENT if `*thread_attrs` is not `NULL`, or
324+
* \return #RCL_RET_INVALID_ARGUMENT if `context->impl->thread_context.thread_attrs` is `NULL`, or
325+
* \return #RCL_RET_OK if the thread attribute was correctly retrieved.
326+
*/
327+
RCL_PUBLIC
328+
RCL_WARN_UNUSED
329+
rcl_ret_t
330+
rcl_context_get_thread_attrs(
331+
const rcl_context_t * context,
332+
rcl_thread_attrs_t ** thread_attrs);
333+
317334
#ifdef __cplusplus
318335
}
319336
#endif

rcl/include/rcl/thread_attr.h

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2023 eSOL Co.,Ltd.
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+
/// @file
16+
17+
#ifndef RCL__THREAD_ATTR_H_
18+
#define RCL__THREAD_ATTR_H_
19+
20+
#ifdef __cplusplus
21+
extern "C"
22+
{
23+
#endif
24+
25+
#include <stddef.h>
26+
27+
#include "rcl/allocator.h"
28+
#include "rcl/macros.h"
29+
#include "rcl/types.h"
30+
#include "rcl/visibility_control.h"
31+
#include "rcl_yaml_param_parser/types.h"
32+
33+
extern const char * const RCL_THREAD_ATTR_VALUE_ENV_VAR;
34+
extern const char * const RCL_THREAD_ATTR_FILE_ENV_VAR;
35+
36+
/// Determine the default thread attribute from string, based on the environment.
37+
/// \param[out] thread_attrs Must not be NULL.
38+
/// \param[in] allocator memory allocator to be used
39+
/// \return #RCL_RET_INVALID_ARGUMENT if an argument is invalid, or
40+
/// \return #RCL_RET_ERROR in case of an unexpected error, or
41+
/// \return #RCL_RET_BAD_ALLOC if allocating memory failed, or
42+
/// \return #RCL_RET_OK.
43+
RCL_PUBLIC
44+
rcl_ret_t
45+
rcl_get_default_thread_attrs_from_value(
46+
rcl_thread_attrs_t * thread_attrs,
47+
rcl_allocator_t allocator);
48+
49+
/// Determine the default thread attribute from file path, based on the environment.
50+
/// \param[out] thread_attrs Must not be NULL.
51+
/// \param[in] allocator memory allocator to be used
52+
/// \return #RCL_RET_INVALID_ARGUMENT if an argument is invalid, or
53+
/// \return #RCL_RET_ERROR in case of an unexpected error, or
54+
/// \return #RCL_RET_BAD_ALLOC if allocating memory failed, or
55+
/// \return #RCL_RET_OK.
56+
RCL_PUBLIC
57+
rcl_ret_t
58+
rcl_get_default_thread_attrs_from_file(
59+
rcl_thread_attrs_t * thread_attrs,
60+
rcl_allocator_t allocator);
61+
62+
#ifdef __cplusplus
63+
}
64+
#endif
65+
66+
#endif // RCL__THREAD_ATTR_H_

rcl/include/rcl/types.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ typedef rmw_ret_t rcl_ret_t;
111111
#define RCL_RET_INVALID_PARAM_RULE 1010
112112
/// Argument is not a valid log level rule
113113
#define RCL_RET_INVALID_LOG_LEVEL_RULE 1020
114+
/// Argument is not a valid thread attr rule
115+
#define RCL_RET_INVALID_THREAD_ATTRS 1030
114116

115117
// rcl event specific ret codes in 20XX
116118
/// Invalid rcl_event_t given return code.

rcl/src/rcl/arguments.c

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "rcl/lexer_lookahead.h"
2626
#include "rcl/validate_topic_name.h"
2727
#include "rcl_yaml_param_parser/parser.h"
28+
#include "rcl_yaml_param_parser/parser_thread_attr.h"
2829
#include "rcl_yaml_param_parser/types.h"
2930
#include "rcutils/allocator.h"
3031
#include "rcutils/error_handling.h"
@@ -286,6 +287,12 @@ rcl_parse_arguments(
286287
goto fail;
287288
}
288289

290+
args_impl->thread_attrs = rcl_get_zero_initialized_thread_attrs();
291+
ret = rcl_thread_attrs_init(&args_impl->thread_attrs, allocator);
292+
if (RCL_RET_OK != ret) {
293+
goto fail;
294+
}
295+
289296
args_impl->parameter_overrides = rcl_yaml_node_struct_init(allocator);
290297
if (NULL == args_impl->parameter_overrides) {
291298
ret = RCL_RET_BAD_ALLOC;
@@ -559,6 +566,73 @@ rcl_parse_arguments(
559566
RCL_DISABLE_FLAG_PREFIX, RCL_LOG_EXT_LIB_FLAG_SUFFIX, rcl_get_error_string().str);
560567
rcl_reset_error();
561568

569+
// Attempt to parse argument as thread attribute flag
570+
if (strcmp(RCL_THREAD_ATTRS_VALUE_FLAG, argv[i]) == 0) {
571+
if (i + 1 < argc) {
572+
if (args_impl->thread_attrs.num_attributes != 0) {
573+
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
574+
"Thread attributes already set: '%s %s'.", argv[i], argv[i + 1]);
575+
++i;
576+
continue;
577+
}
578+
// Attempt to parse next argument as thread attribute rule
579+
if (RCL_RET_OK ==
580+
rcl_parse_yaml_thread_attrs_value(argv[i + 1], &args_impl->thread_attrs))
581+
{
582+
RCUTILS_LOG_DEBUG_NAMED(
583+
ROS_PACKAGE_NAME, "Got thread attribute rule : %s\n", argv[i + 1]);
584+
++i; // Skip flag here, for loop will skip rule.
585+
continue;
586+
}
587+
rcl_error_string_t prev_error_string = rcl_get_error_string();
588+
rcl_reset_error();
589+
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
590+
"Couldn't parse thread attribute rule: '%s %s'. Error: %s", argv[i], argv[i + 1],
591+
prev_error_string.str);
592+
} else {
593+
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
594+
"Couldn't parse trailing %s flag. No thread attribute rule found.", argv[i]);
595+
}
596+
ret = RCL_RET_INVALID_ROS_ARGS;
597+
goto fail;
598+
}
599+
RCUTILS_LOG_DEBUG_NAMED(
600+
ROS_PACKAGE_NAME, "Arg %d (%s) is not a %s flag.",
601+
i, argv[i], RCL_THREAD_ATTRS_VALUE_FLAG);
602+
603+
// Attempt to parse argument as thread attribute file rule
604+
if (strcmp(RCL_THREAD_ATTRS_FILE_FLAG, argv[i]) == 0) {
605+
if (i + 1 < argc) {
606+
if (args_impl->thread_attrs.num_attributes != 0) {
607+
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
608+
"Thread attributes already setted: '%s %s'.", argv[i], argv[i + 1]);
609+
++i;
610+
continue;
611+
}
612+
// Attempt to parse next argument as thread attribute file rule
613+
if (
614+
RCL_RET_OK == rcl_parse_yaml_thread_attrs_file(
615+
argv[i + 1], &args_impl->thread_attrs))
616+
{
617+
++i; // Skip flag here, for loop will skip rule.
618+
continue;
619+
}
620+
rcl_error_string_t prev_error_string = rcl_get_error_string();
621+
rcl_reset_error();
622+
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
623+
"Couldn't parse thread attr file: '%s %s'. Error: %s", argv[i], argv[i + 1],
624+
prev_error_string.str);
625+
} else {
626+
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
627+
"Couldn't parse trailing %s flag. No file path provided.", argv[i]);
628+
}
629+
ret = RCL_RET_INVALID_ROS_ARGS;
630+
goto fail;
631+
}
632+
RCUTILS_LOG_DEBUG_NAMED(
633+
ROS_PACKAGE_NAME, "Arg %d (%s) is not a %s flag.",
634+
i, argv[i], RCL_THREAD_ATTRS_FILE_FLAG);
635+
562636
// Argument is an unknown ROS specific argument
563637
args_impl->unparsed_ros_args[args_impl->num_unparsed_ros_args] = i;
564638
++(args_impl->num_unparsed_ros_args);
@@ -988,6 +1062,14 @@ rcl_arguments_fini(
9881062
args->impl->external_log_config_file = NULL;
9891063
}
9901064

1065+
rcl_ret_t thread_ret = rcl_thread_attrs_fini(&args->impl->thread_attrs);
1066+
if (thread_ret != RCL_RET_OK) {
1067+
ret = thread_ret;
1068+
RCUTILS_LOG_ERROR_NAMED(
1069+
ROS_PACKAGE_NAME,
1070+
"Failed to finalize thread attribute while finalizing arguments. Continuing...");
1071+
}
1072+
9911073
args->impl->allocator.deallocate(args->impl, args->impl->allocator.state);
9921074
args->impl = NULL;
9931075
return ret;
@@ -2067,11 +2149,29 @@ _rcl_allocate_initialized_arguments_impl(rcl_arguments_t * args, rcl_allocator_t
20672149
args_impl->log_rosout_disabled = false;
20682150
args_impl->log_ext_lib_disabled = false;
20692151
args_impl->enclave = NULL;
2152+
args_impl->thread_attrs = rcl_get_zero_initialized_thread_attrs();
20702153
args_impl->allocator = *allocator;
20712154

20722155
return RCL_RET_OK;
20732156
}
20742157

2158+
rcl_ret_t
2159+
rcl_arguments_get_thread_attrs(
2160+
const rcl_arguments_t * arguments,
2161+
rcl_thread_attrs_t ** thread_attrs)
2162+
{
2163+
RCL_CHECK_ARGUMENT_FOR_NULL(arguments, RCL_RET_INVALID_ARGUMENT);
2164+
RCL_CHECK_ARGUMENT_FOR_NULL(arguments->impl, RCL_RET_INVALID_ARGUMENT);
2165+
RCL_CHECK_ARGUMENT_FOR_NULL(thread_attrs, RCL_RET_INVALID_ARGUMENT);
2166+
2167+
if (0 < arguments->impl->thread_attrs.num_attributes) {
2168+
*thread_attrs = &arguments->impl->thread_attrs;
2169+
return RCL_RET_OK;
2170+
} else {
2171+
return RCL_RET_ERROR;
2172+
}
2173+
}
2174+
20752175
#ifdef __cplusplus
20762176
}
20772177
#endif

rcl/src/rcl/arguments_impl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ struct rcl_arguments_impl_s
5151
/// Length of remap_rules.
5252
int num_remap_rules;
5353

54+
/// thread attribute.
55+
rcl_thread_attrs_t thread_attrs;
56+
5457
/// Log levels parsed from arguments.
5558
rcl_log_levels_t log_levels;
5659
/// A file used to configure the external logging library

rcl/src/rcl/context.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ extern "C"
2424
#include "./common.h"
2525
#include "./context_impl.h"
2626
#include "rcutils/stdatomic_helper.h"
27+
#include "rcl_yaml_param_parser/parser_thread_attr.h"
2728

2829
rcl_context_t
2930
rcl_get_zero_initialized_context(void)
@@ -105,6 +106,22 @@ rcl_context_get_rmw_context(rcl_context_t * context)
105106
return &(context->impl->rmw_context);
106107
}
107108

109+
rcl_ret_t
110+
rcl_context_get_thread_attrs(
111+
const rcl_context_t * context,
112+
rcl_thread_attrs_t ** thread_attrs)
113+
{
114+
RCL_CHECK_ARGUMENT_FOR_NULL(context, RCL_RET_INVALID_ARGUMENT);
115+
RCL_CHECK_ARGUMENT_FOR_NULL(context->impl, RCL_RET_INVALID_ARGUMENT);
116+
117+
if (0 < context->impl->thread_attrs.num_attributes) {
118+
*thread_attrs = &context->impl->thread_attrs;
119+
return RCL_RET_OK;
120+
} else {
121+
return RCL_RET_ERROR;
122+
}
123+
}
124+
108125
rcl_ret_t
109126
__cleanup_context(rcl_context_t * context)
110127
{
@@ -146,6 +163,21 @@ __cleanup_context(rcl_context_t * context)
146163
}
147164
}
148165

166+
// clean up thread_attrs_context
167+
rcl_ret_t thread_attrs_context_fini_ret =
168+
rcl_thread_attrs_fini(&(context->impl->thread_attrs));
169+
if (RCL_RET_OK != thread_attrs_context_fini_ret) {
170+
if (RCL_RET_OK == ret) {
171+
ret = thread_attrs_context_fini_ret;
172+
}
173+
RCUTILS_SAFE_FWRITE_TO_STDERR(
174+
"[rcl|context.c:" RCUTILS_STRINGIFY(__LINE__)
175+
"] failed to finalize attr context while cleaning up context, memory may be leaked: ");
176+
RCUTILS_SAFE_FWRITE_TO_STDERR(rcutils_get_error_string().str);
177+
RCUTILS_SAFE_FWRITE_TO_STDERR("\n");
178+
rcutils_reset_error();
179+
}
180+
149181
// clean up rmw_context
150182
if (NULL != context->impl->rmw_context.implementation_identifier) {
151183
rmw_ret_t rmw_context_fini_ret = rmw_context_fini(&(context->impl->rmw_context));

rcl/src/rcl/context_impl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifndef RCL__CONTEXT_IMPL_H_
1616
#define RCL__CONTEXT_IMPL_H_
1717

18+
#include "rcl_yaml_param_parser/parser_thread_attr.h"
1819
#include "rcl/context.h"
1920
#include "rcl/error_handling.h"
2021

@@ -38,6 +39,8 @@ struct rcl_context_impl_s
3839
char ** argv;
3940
/// rmw context.
4041
rmw_context_t rmw_context;
42+
/// thread attributes.
43+
rcl_thread_attrs_t thread_attrs;
4144
};
4245

4346
RCL_LOCAL

0 commit comments

Comments
 (0)