diff --git a/third-party/rsutils/include/rsutils/concurrency/control-c-handler.h b/third-party/rsutils/include/rsutils/concurrency/control-c-handler.h new file mode 100644 index 0000000000..54d9195fb8 --- /dev/null +++ b/third-party/rsutils/include/rsutils/concurrency/control-c-handler.h @@ -0,0 +1,33 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2024 Intel Corporation. All Rights Reserved. +#pragma once + + +namespace rsutils { +namespace concurrency { + + +// A handler for catching Control-C input from the user +// +// NOTE: only a single instance should be in use: using more than one has undefined behavior +// +class control_c_handler +{ +public: + control_c_handler(); + ~control_c_handler(); + + // Return true if a Control-C happened + bool was_pressed() const; + + // Make was_pressed() return false + void reset(); + + // Block until a Control-C happens + // If already was_pressed(), the state is cleared and a new Control-C is waited on + void wait(); +}; + + +} // namespace concurrency +} // namespace rsutils diff --git a/third-party/rsutils/include/rsutils/concurrency/event.h b/third-party/rsutils/include/rsutils/concurrency/event.h new file mode 100644 index 0000000000..99b76a9abd --- /dev/null +++ b/third-party/rsutils/include/rsutils/concurrency/event.h @@ -0,0 +1,100 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2024 Intel Corporation. All Rights Reserved. +#pragma once + +#include +#include +#include + + +namespace rsutils { +namespace concurrency { + + +// A Python Event-like implementation, simplifying condition-variable and mutex access +// +class event +{ + mutable std::condition_variable _cv; + mutable std::mutex _m; + bool _is_set; + +public: + event() + : _is_set( false ) + { + } + + // Return true if the event was set() + bool is_set() const + { + std::unique_lock< std::mutex > lock( _m ); + return _is_set; + } + + operator bool() const { return is_set(); } + + // Trigger the event + // Threads waiting on it will wake up + void set() + { + { + std::unique_lock< std::mutex > lock( _m ); + _is_set = true; + } + _cv.notify_all(); + } + + // Untrigger the event + // Does not affect any threads + void clear() + { + std::unique_lock< std::mutex > lock( _m ); + _is_set = false; + } + + // Block until the event is set() + // If already set, returns immediately + // The event remains set when returning: it needs to be cleared... + void wait() const + { + std::unique_lock< std::mutex > lock( _m ); + if( ! _is_set ) + _cv.wait( lock ); + } + + // Clear and block until the event is set() again + void clear_and_wait() + { + std::unique_lock< std::mutex > lock( _m ); + _is_set = false; + _cv.wait( lock ); + } + + // Block until the event is set(), or the timeout occurs + // If already set, returns immediately + // Returns true unless a timeout occurred + template< class Rep, class Period > + bool wait( std::chrono::duration< Rep, Period > const & timeout ) const + { + std::unique_lock< std::mutex > lock( _m ); + if( ! _is_set ) + _is_set = ( std::cv_status::timeout != _cv.wait( lock, timeout ) ); + return _is_set; + } + + // Clear and block until the event is set() again or timeout occurs + // Returns true unless a timeout occurred + template< class Rep, class Period > + bool clear_and_wait( std::chrono::duration< Rep, Period > const & timeout ) + { + std::unique_lock< std::mutex > lock( _m ); + _is_set = false; + _is_set = ( std::cv_status::timeout != _cv.wait( lock, timeout ) ); + return _is_set; + } +}; + + +} // namespace concurrency +} // namespace rsutils diff --git a/third-party/rsutils/src/control-c-handler.cpp b/third-party/rsutils/src/control-c-handler.cpp new file mode 100644 index 0000000000..7d73013ce7 --- /dev/null +++ b/third-party/rsutils/src/control-c-handler.cpp @@ -0,0 +1,86 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2024 Intel Corporation. All Rights Reserved. +#include +#include +#include + +#include +#include + + +namespace rsutils { +namespace concurrency { + + +namespace { + +event sigint_ev; +std::atomic_bool in_use( false ); + + +void handle_signal( int signal ) +{ + switch( signal ) + { + case SIGINT: + sigint_ev.set(); + break; + } +} + +} // namespace + + +control_c_handler::control_c_handler() +{ + if( in_use.exchange( true ) ) + throw std::runtime_error( "a Control-C handler is already in effect" ); +#ifdef _WIN32 + signal( SIGINT, handle_signal ); +#else + struct sigaction sa; + sa.sa_handler = &handle_signal; + sa.sa_flags = SA_RESTART; + sigfillset( &sa.sa_mask ); + if( sigaction( SIGINT, &sa, NULL ) == -1 ) + LOG_ERROR( "Cannot install SIGINT handler" ); +#endif +} + + +control_c_handler::~control_c_handler() +{ +#ifdef _WIN32 + signal( SIGINT, SIG_DFL ); +#else + struct sigaction sa; + sa.sa_handler = SIG_DFL; + sa.sa_flags = SA_RESTART; + sigfillset( &sa.sa_mask ); + if( sigaction( SIGINT, &sa, NULL ) == -1 ) + LOG_DEBUG( "cannot uninstall SIGINT handler" ); +#endif + in_use = false; +} + + +bool control_c_handler::was_pressed() const +{ + return sigint_ev.is_set(); +} + + +void control_c_handler::reset() +{ + sigint_ev.clear(); +} + + +void control_c_handler::wait() +{ + sigint_ev.clear_and_wait(); +} + + +} // namespace concurrency +} // namespace rsutils diff --git a/tools/dds/dds-adapter/rs-dds-adapter.cpp b/tools/dds/dds-adapter/rs-dds-adapter.cpp index 30e43efc4a..e30ec392ed 100644 --- a/tools/dds/dds-adapter/rs-dds-adapter.cpp +++ b/tools/dds/dds-adapter/rs-dds-adapter.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -195,8 +196,10 @@ try device_handlers_list.erase( dev ); } ); - std::cin.ignore(std::numeric_limits::max(), 0);// Pend until CTRL + C is pressed - + { + rsutils::concurrency::control_c_handler control_c; + control_c.wait(); + } std::cout << "Shutting down rs-dds-adapter..." << std::endl; return EXIT_SUCCESS; diff --git a/unit-tests/dds/adapter/test-depth.py b/unit-tests/dds/adapter/test-depth.py new file mode 100644 index 0000000000..d75037eae4 --- /dev/null +++ b/unit-tests/dds/adapter/test-depth.py @@ -0,0 +1,86 @@ +# License: Apache 2.0. See LICENSE file in root directory. +# Copyright(c) 2024 Intel Corporation. All Rights Reserved. + +#test:donotrun:!dds +#test:device D400* + +from rspy import log, repo, test +import time + + +with test.closure( 'Run rs-dds-adapter', on_fail=test.ABORT ): + adapter_path = repo.find_built_exe( 'tools/dds/dds-adapter', 'rs-dds-adapter' ) + if test.check( adapter_path ): + import subprocess, signal + cmd = [adapter_path, '--domain-id', '123'] + if log.is_debug_on(): + cmd.append( '--debug' ) + adapter_process = subprocess.Popen( cmd, + stdout=None, + stderr=subprocess.STDOUT, + universal_newlines=True ) # don't fail on errors + +from rspy import librs as rs +if log.is_debug_on(): + rs.log_to_console( rs.log_severity.debug ) + +with test.closure( 'Initialize librealsense context', on_fail=test.ABORT ): + context = rs.context( { 'dds': { 'enabled': True, 'domain': 123, 'participant': 'client' }} ) + +with test.closure( 'Wait for a device', on_fail=test.ABORT ): + # Note: takes time for a device to enumerate, and more to get it discovered + dev = rs.wait_for_devices( context, rs.only_sw_devices, n=1., timeout=8 ) + +with test.closure( 'Get sensor', on_fail=test.ABORT ): + sensor = dev.first_depth_sensor() + test.check( sensor ) + +with test.closure( 'Find profile', on_fail=test.ABORT ): + for p in sensor.profiles: + log.d( p ) + profile = next( p for p in sensor.profiles + if p.fps() == 30 + and p.stream_type() == rs.stream.depth ) + test.check( profile ) + +n_frames = 0 +start_time = None +def frame_callback( frame ): + global n_frames, start_time + if n_frames == 0: + start_time = time.perf_counter() + n_frames += 1 + +with test.closure( f'Stream {profile}', on_fail=test.ABORT ): + sensor.open( [profile] ) + sensor.start( frame_callback ) + +with test.closure( 'Let it stream' ): + time.sleep( 3 ) + end_time = time.perf_counter() + if test.check( n_frames > 0 ): + test.info( 'start_time', start_time ) + test.info( 'end_time', end_time ) + test.info( 'n_frames', n_frames ) + test.check_approx_abs( n_frames / (end_time-start_time), 29.5, 1 ) + +with test.closure( 'Open the same profile while streaming!' ): + test.check_throws( lambda: + sensor.open( [profile] ), + RuntimeError, 'open(...) failed. Software device is streaming!' ) + +with test.closure( 'Stop streaming' ): + sensor.stop() + sensor.close() + +del profile +del sensor +del dev +del context + +with test.closure( 'Stop rs-dds-adapter' ): + adapter_process.send_signal( signal.SIGTERM ) + adapter_process.wait( timeout=2 ) + +test.print_results() + diff --git a/unit-tests/dds/test-librs-connections.py b/unit-tests/dds/test-librs-connections.py index 9e251f68c3..256f53b00c 100644 --- a/unit-tests/dds/test-librs-connections.py +++ b/unit-tests/dds/test-librs-connections.py @@ -9,7 +9,7 @@ import d435i import d405 -import librs as rs +from rspy import librs as rs if log.is_debug_on(): rs.log_to_console( rs.log_severity.debug ) from time import sleep diff --git a/unit-tests/dds/test-librs-device-properties.py b/unit-tests/dds/test-librs-device-properties.py index 9d83d13fb5..192bf8e4fe 100644 --- a/unit-tests/dds/test-librs-device-properties.py +++ b/unit-tests/dds/test-librs-device-properties.py @@ -10,7 +10,7 @@ import d435i import d405 import d455 -import librs as rs +from rspy import librs as rs if log.is_debug_on(): rs.log_to_console( rs.log_severity.debug ) diff --git a/unit-tests/dds/test-librs-extrinsics.py b/unit-tests/dds/test-librs-extrinsics.py index 8f6f337b23..ed3293f293 100644 --- a/unit-tests/dds/test-librs-extrinsics.py +++ b/unit-tests/dds/test-librs-extrinsics.py @@ -7,7 +7,7 @@ from rspy import log, test log.nested = 'C ' -import librs as rs +from rspy import librs as rs if log.is_debug_on(): rs.log_to_console( rs.log_severity.debug ) from time import sleep diff --git a/unit-tests/dds/test-librs-formats-conversion.py b/unit-tests/dds/test-librs-formats-conversion.py index 37b8010043..81a97e9c02 100644 --- a/unit-tests/dds/test-librs-formats-conversion.py +++ b/unit-tests/dds/test-librs-formats-conversion.py @@ -5,7 +5,7 @@ #test:retries:gha 2 from rspy import log, test -import librs as rs +from rspy import librs as rs if log.is_debug_on(): rs.log_to_console( rs.log_severity.debug ) diff --git a/unit-tests/dds/test-librs-intrinsics.py b/unit-tests/dds/test-librs-intrinsics.py index 7c72a2ce83..78aef946fd 100644 --- a/unit-tests/dds/test-librs-intrinsics.py +++ b/unit-tests/dds/test-librs-intrinsics.py @@ -69,7 +69,7 @@ def close_server( instance ): log.nested = 'C ' - import librs as rs + from rspy import librs as rs if log.is_debug_on(): rs.log_to_console( rs.log_severity.debug ) diff --git a/unit-tests/dds/test-librs-options.py b/unit-tests/dds/test-librs-options.py index 105071b04a..7c21156ce5 100644 --- a/unit-tests/dds/test-librs-options.py +++ b/unit-tests/dds/test-librs-options.py @@ -47,7 +47,7 @@ def _on_set_option( server, option, value ): ############################################################################################################### # The client is LibRS # - import librs as rs + from rspy import librs as rs if log.is_debug_on(): rs.log_to_console( rs.log_severity.debug ) diff --git a/unit-tests/dds/test-metadata.py b/unit-tests/dds/test-metadata.py index 0c28dc980b..75a4b4421e 100644 --- a/unit-tests/dds/test-metadata.py +++ b/unit-tests/dds/test-metadata.py @@ -181,7 +181,7 @@ def __exit__( self, type, value, traceback ): ############################################################################################# # with test.closure( "Initialize librs device", on_fail=test.ABORT ): - import librs as rs + from rspy import librs as rs if log.is_debug_on(): rs.log_to_console( rs.log_severity.debug ) context = rs.context( { 'dds': { 'enabled': True, 'domain': 123, 'participant': 'librs' }} ) diff --git a/unit-tests/log/test-vs-LOG-static.cpp b/unit-tests/log/test-vs-LOG-static.cpp index f2142c32f6..b5c240985e 100644 --- a/unit-tests/log/test-vs-LOG-static.cpp +++ b/unit-tests/log/test-vs-LOG-static.cpp @@ -25,7 +25,7 @@ using namespace librealsense; static void dummy_callback( rs2_log_severity, rs2_log_message const *, void * ) { -}; +} TEST_CASE( "rs2_log vs LOG() - internal", "[log]" ) diff --git a/unit-tests/dds/librs.py b/unit-tests/py/rspy/librs.py similarity index 100% rename from unit-tests/dds/librs.py rename to unit-tests/py/rspy/librs.py