From 99ee88a686bf59c25509582bdeb5d7951218b14d Mon Sep 17 00:00:00 2001 From: Deans Date: Mon, 20 Mar 2023 17:53:01 -0500 Subject: [PATCH] Add more Variable Server unit and integration tests, clean up and clarify naming --- .github/workflows/code_coverage.yml | 2 +- .github/workflows/test_linux.yml | 11 +- CMakeLists.txt | 4 +- docs/developer_docs/Variable-Server.md | 446 +++++++ .../images/var_add_sequence.png | Bin 0 -> 77896 bytes docs/developer_docs/images/vs_uml.png | Bin 0 -> 243270 bytes .../simulation_capabilities/Threads.md | 2 +- include/trick/ClientConnection.hh | 15 +- include/trick/ClientListener.hh | 63 - include/trick/MulticastGroup.hh | 55 + include/trick/MulticastManager.hh | 28 - include/trick/SysThread.hh | 22 + include/trick/SystemInterface.hh | 5 +- include/trick/TCPClientListener.hh | 62 + include/trick/TCPConnection.hh | 23 +- include/trick/UDPConnection.hh | 12 +- include/trick/VariableReference.hh | 42 +- include/trick/VariableServer.hh | 32 +- include/trick/VariableServerListenThread.hh | 27 +- include/trick/VariableServerSession.hh | 319 ++--- include/trick/VariableServerSessionThread.hh | 106 ++ include/trick/VariableServerThread.hh | 127 -- include/trick/var_binary_parser.hh | 1 - .../trick/tests/test_variable_server.py | 14 +- share/trick/sim_objects/default_trick_sys.sm | 16 +- share/trick/trickops/WorkflowCommon.py | 2 +- test/.gitignore | 1 + test/SIM_test_varserv/RUN_test/err1_test.py | 24 + test/SIM_test_varserv/RUN_test/err2_test.py | 24 + test/SIM_test_varserv/RUN_test/manual_test.py | 2 +- test/SIM_test_varserv/RUN_test/unit_test.py | 9 +- test/SIM_test_varserv/S_overrides.mk | 10 +- test/SIM_test_varserv/file_to_send.txt | 1 + .../models/test_client/.gitignore | 4 +- .../models/test_client/test_client.cpp | 556 ++++---- .../models/test_client/test_client.hh | 238 ++++ .../models/test_client/test_client_err1.cpp | 13 + .../models/test_client/test_client_err2.cpp | 13 + .../models/varserv/include/VS.hh | 19 +- .../models/varserv/src/VS.cpp | 26 +- test_sims.yml | 9 +- trick_source/sim_services/CMakeLists.txt | 34 +- .../sim_services/InputProcessor/Makefile_deps | 2 +- .../sim_services/ThreadBase/SysThread.cpp | 46 +- .../sim_services/ThreadBase/ThreadBase.cpp | 1 - .../sim_services/VariableServer/Makefile_deps | 206 +-- .../VariableServer/VariableReference.cpp | 385 +++--- .../VariableServer/VariableServer.cpp | 32 +- .../VariableServerListenThread.cpp | 194 +-- .../VariableServer/VariableServerSession.cpp | 166 ++- .../VariableServerSessionThread.cpp | 134 ++ ...p => VariableServerSessionThread_loop.cpp} | 103 +- .../VariableServerSession_commands.cpp | 121 +- ...ableServerSession_copy_and_write_modes.cpp | 119 ++ .../VariableServerSession_copy_data.cpp | 90 -- .../VariableServerSession_copy_sim_data.cpp | 8 +- .../VariableServerSession_freeze_init.cpp | 6 +- .../VariableServerSession_write_data.cpp | 42 +- .../VariableServerSession_write_stdio.cpp | 2 +- .../VariableServer/VariableServerThread.cpp | 159 --- .../VariableServer_copy_and_write_freeze.cpp | 13 + ...erver_copy_and_write_freeze_scheduled.cpp} | 15 +- ...riableServer_copy_and_write_scheduled.cpp} | 13 +- .../VariableServer_copy_and_write_top.cpp | 13 + .../VariableServer_copy_data_freeze.cpp | 18 - .../VariableServer_copy_data_top.cpp | 18 - .../VariableServer_freeze_init.cpp | 14 +- ...riableServer_get_next_freeze_call_time.cpp | 13 +- ...VariableServer_get_next_sync_call_time.cpp | 11 +- .../VariableServer_get_var_server_port.cpp | 44 - ...VariableServer_open_additional_servers.cpp | 88 ++ .../VariableServer/VariableServer_restart.cpp | 27 +- .../VariableServer_shutdown.cpp | 7 +- .../VariableServer/exit_var_thread.cpp | 8 +- .../sim_services/VariableServer/test/Makefile | 27 +- .../test/MockClientConnection.hh | 24 + .../VariableServer/test/MockMulticastGroup.hh | 18 + .../test/MockTCPClientListener.hh | 24 + .../VariableServer/test/MockTCPConnection.hh | 21 + .../test/MockVariableServerSession.hh | 33 + .../VariableServer/test/TestConnection.hh | 80 -- .../test/VariableReference_test.cc | 4 +- ...VariableReference_writeValueBinary_test.cc | 6 +- .../test/VariableServerListenThread_test.cc | 392 ++++++ .../test/VariableServerSessionThread_test.cc | 357 +++++ .../test/VariableServerSession_test.cc | 1148 ++++++++++++++++- .../test/VariableServer_test.cc | 112 ++ .../VariableServer/var_server_ext.cpp | 59 +- .../connection_handlers/ClientConnection.cpp | 11 +- .../connection_handlers/MulticastGroup.cpp | 228 ++++ .../connection_handlers/MulticastManager.cpp | 65 - ...ientListener.cpp => TCPClientListener.cpp} | 74 +- .../connection_handlers/TCPConnection.cpp | 59 +- .../connection_handlers/UDPConnection.cpp | 73 +- .../test/MulticastGroup_test.cpp | 136 ++ .../SystemInterfaceMock.hh | 16 +- ...er_test.cpp => TCPClientListener_test.cpp} | 66 +- .../test/TCPConnection_test.cpp | 32 +- .../test/UDPConnection_test.cpp | 22 +- .../src/var_binary_parser.cc | 18 - .../test/TEST_var_binary_parser.cc | 21 +- trickops.py | 32 +- 102 files changed, 5566 insertions(+), 2129 deletions(-) create mode 100644 docs/developer_docs/Variable-Server.md create mode 100644 docs/developer_docs/images/var_add_sequence.png create mode 100644 docs/developer_docs/images/vs_uml.png delete mode 100644 include/trick/ClientListener.hh create mode 100644 include/trick/MulticastGroup.hh delete mode 100644 include/trick/MulticastManager.hh create mode 100644 include/trick/TCPClientListener.hh create mode 100644 include/trick/VariableServerSessionThread.hh delete mode 100644 include/trick/VariableServerThread.hh create mode 100644 test/SIM_test_varserv/RUN_test/err1_test.py create mode 100644 test/SIM_test_varserv/RUN_test/err2_test.py create mode 100644 test/SIM_test_varserv/file_to_send.txt create mode 100644 test/SIM_test_varserv/models/test_client/test_client.hh create mode 100644 test/SIM_test_varserv/models/test_client/test_client_err1.cpp create mode 100644 test/SIM_test_varserv/models/test_client/test_client_err2.cpp create mode 100644 trick_source/sim_services/VariableServer/VariableServerSessionThread.cpp rename trick_source/sim_services/VariableServer/{VariableServerThread_loop.cpp => VariableServerSessionThread_loop.cpp} (50%) create mode 100644 trick_source/sim_services/VariableServer/VariableServerSession_copy_and_write_modes.cpp delete mode 100644 trick_source/sim_services/VariableServer/VariableServerSession_copy_data.cpp delete mode 100644 trick_source/sim_services/VariableServer/VariableServerThread.cpp create mode 100644 trick_source/sim_services/VariableServer/VariableServer_copy_and_write_freeze.cpp rename trick_source/sim_services/VariableServer/{VariableServer_copy_data_freeze_scheduled.cpp => VariableServer_copy_and_write_freeze_scheduled.cpp} (52%) rename trick_source/sim_services/VariableServer/{VariableServer_copy_data_scheduled.cpp => VariableServer_copy_and_write_scheduled.cpp} (57%) create mode 100644 trick_source/sim_services/VariableServer/VariableServer_copy_and_write_top.cpp delete mode 100644 trick_source/sim_services/VariableServer/VariableServer_copy_data_freeze.cpp delete mode 100644 trick_source/sim_services/VariableServer/VariableServer_copy_data_top.cpp delete mode 100644 trick_source/sim_services/VariableServer/VariableServer_get_var_server_port.cpp create mode 100644 trick_source/sim_services/VariableServer/VariableServer_open_additional_servers.cpp create mode 100644 trick_source/sim_services/VariableServer/test/MockClientConnection.hh create mode 100644 trick_source/sim_services/VariableServer/test/MockMulticastGroup.hh create mode 100644 trick_source/sim_services/VariableServer/test/MockTCPClientListener.hh create mode 100644 trick_source/sim_services/VariableServer/test/MockTCPConnection.hh create mode 100644 trick_source/sim_services/VariableServer/test/MockVariableServerSession.hh delete mode 100644 trick_source/sim_services/VariableServer/test/TestConnection.hh create mode 100644 trick_source/sim_services/VariableServer/test/VariableServerListenThread_test.cc create mode 100644 trick_source/sim_services/VariableServer/test/VariableServerSessionThread_test.cc create mode 100644 trick_source/sim_services/VariableServer/test/VariableServer_test.cc create mode 100644 trick_source/trick_utils/connection_handlers/MulticastGroup.cpp delete mode 100644 trick_source/trick_utils/connection_handlers/MulticastManager.cpp rename trick_source/trick_utils/connection_handlers/{ClientListener.cpp => TCPClientListener.cpp} (70%) create mode 100644 trick_source/trick_utils/connection_handlers/test/MulticastGroup_test.cpp rename trick_source/trick_utils/connection_handlers/test/{ClientListener_test.cpp => TCPClientListener_test.cpp} (79%) diff --git a/.github/workflows/code_coverage.yml b/.github/workflows/code_coverage.yml index 27f90b37..5cf64923 100644 --- a/.github/workflows/code_coverage.yml +++ b/.github/workflows/code_coverage.yml @@ -29,7 +29,7 @@ jobs: - name: Install GTest run: | dnf config-manager --enable ol8_codeready_builder - dnf install -y gtest-devel + dnf install -y gtest-devel gmock-devel - name: Checkout repository uses: actions/checkout@master diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 42637d40..f5e499d1 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -37,6 +37,7 @@ jobs: maven cmake zip + gdb install_gtest: echo gtest already installed conf_pkg: echo package manager already configured install_cmd: install -y @@ -64,7 +65,7 @@ jobs: python3-pip python3-venv install_gtest: | - apt-get install -y libgtest-dev + apt-get install -y libgtest-dev libgmock-dev cd /usr/src/gtest cmake . make @@ -100,7 +101,9 @@ jobs: libX11-devel libXt-devel swig3 - gtest-devel + install_gtest: | + yum install -y http://repo.okay.com.mx/centos/7/x86_64/release/okay-release-1-6.el7.noarch.rpm + yum install -y gtest gtest-devel gmock gmock-devel #-------- RHEL 8-based Only Dependencies ---------------- - cfg: { arch: rhel, arch_ver: 8 } pkg_mgr: dnf @@ -113,12 +116,12 @@ jobs: dnf install -y 'dnf-command(config-manager)' install_gtest: | dnf config-manager --enable powertools - dnf install -y gtest-devel + dnf install -y gtest-devel gmock-devel #-------- OS and Version Specific Dependencies ---------------- - cfg: { os: oraclelinux } install_gtest: | dnf config-manager --enable ol8_codeready_builder - dnf install -y gtest-devel + dnf install -y gtest-devel gmock-devel #-------- Job definition ---------------- runs-on: ubuntu-latest container: docker://${{matrix.cfg.os}}:${{matrix.cfg.tag}} diff --git a/CMakeLists.txt b/CMakeLists.txt index 99e56a2a..f40375c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,7 +175,7 @@ set( IO_SRC ${CMAKE_BINARY_DIR}/temp_src/io_src/io_JITEvent.cpp ${CMAKE_BINARY_DIR}/temp_src/io_src/io_JITInputFile.cpp ${CMAKE_BINARY_DIR}/temp_src/io_src/io_JSONVariableServer.cpp - ${CMAKE_BINARY_DIR}/temp_src/io_src/io_JSONVariableServerThread.cpp + ${CMAKE_BINARY_DIR}/temp_src/io_src/io_JSONVariableServerSessionThread.cpp ${CMAKE_BINARY_DIR}/temp_src/io_src/io_JobData.cpp ${CMAKE_BINARY_DIR}/temp_src/io_src/io_MM4_Integrator.cpp ${CMAKE_BINARY_DIR}/temp_src/io_src/io_MSConnect.cpp @@ -231,7 +231,7 @@ set( IO_SRC ${CMAKE_BINARY_DIR}/temp_src/io_src/io_VariableServer.cpp ${CMAKE_BINARY_DIR}/temp_src/io_src/io_VariableServerListenThread.cpp ${CMAKE_BINARY_DIR}/temp_src/io_src/io_VariableServerReference.cpp - ${CMAKE_BINARY_DIR}/temp_src/io_src/io_VariableServerThread.cpp + ${CMAKE_BINARY_DIR}/temp_src/io_src/io_VariableServerSessionThread.cpp ${CMAKE_BINARY_DIR}/temp_src/io_src/io_Zeroconf.cpp ${CMAKE_BINARY_DIR}/temp_src/io_src/io_attributes.cpp ${CMAKE_BINARY_DIR}/temp_src/io_src/io_dllist.cpp diff --git a/docs/developer_docs/Variable-Server.md b/docs/developer_docs/Variable-Server.md new file mode 100644 index 00000000..f2d3e36e --- /dev/null +++ b/docs/developer_docs/Variable-Server.md @@ -0,0 +1,446 @@ +# Variable Server Refactor + +The goals of this refactor were as follows: + +1. Test the Trick Variable Server + - At the start, the variable server was completely uncovered by CI. This can be seen in the earliest builds uploaded to Coveralls (https://coveralls.io/builds/53258598) + - Identified a need for both unit and sim tests + - Significant refactoring was needed to be able to do any unit tests +2. Create a generic representation for a session that can be used in the webserver as well + - Currently, the webserver implementation is completely separate from the variable server, even through they do they same thing. There is a lot of repeated functionality between the two. + - The current implementation of the variable server made it impossible to reuse any of its code. +3. Clarify and document code. + + +## Class Structure + +### VariableServer.hh +The overarching variable server object. It is instantiated in VariableServerSimObject and is intended to be a singleton. + +Responsibilities: +- Initialize a listen thread +- Keep track of all sessions +- Interface with the Trick scheduler through the Trick jobs, and delegate to sessions +- Provide the API for all variable server commands to go through, and delegate to correct session + + +### VariableServerListenThread.hh +Opens a TCP port and listens for new client connections until shut down. Only 1 exists by default as a member of VariableServer, +but additional listen threads can be created through a call to trick.var_server_create_tcp_socket() + +Responsibilities: +- Open a TCP port for listening +- Continuously listen on the port +- Broadcast the port on the multicast channel +- When a client connection comes in, set up the connection, start a thread, and wait for the thread to start the connection + - Delete the thread if the connection fails + +### VariableServerSessionThread.hh +Runs asynchronous parts for the single VariableServerSession. + +Responsibilities: + +- Accept the connection and pass it into the VariableServerSession +- Delete the session and close the connection on shutdown +- Run the parts of VariableServerSession that run asynchronously until user commands shutdown or sim shutdown, including: + - Tell VSSession to read from connection and execute command + - Tell VSSession to copy sim data (if in correct mode) + - Tell VSSession to write data (if in correct mode) + + +### VariableServerSession.hh +Track information and execute commands for a single client session. + +Responsibilities: +- Keep track of the list of variables +- Track desired copy/write mode and timing +- Overrideable behaviors: + - Write out to connection when commanded + - Read in and execute commands from connection when commanded + +### VariableReference.hh +Represent a single variable in a session + +Responsibilities: +- Copy sim data into internal buffer when commanded +- Write sim data into stream when commanded +- Provide functions to write ASCII and binary data +- Track what units the user has requested +- Survive a checkpoint restart + +### TCPClientListener.hh +Represents a listening TCP server + +Responsibilities: +- Encapsulate network code +- Provide an interface to start a server, listen for new connections, and create new connections +- Create a TCPConnection when a client connects + +### ClientConnection.hh + +Responsibilities +Abstract class representing a connection to a client. Inherited classes to handle TCP, UDP, and Multicast connections +- Encapsulate network code +- Provide a generic interface for initializing, starting, shutting down, reading, and writing to network regardless of connection type + +## Threading changes + +### VariableServerListenThread is required to wait for VariableServerSessionThread to notify it when the connection has been accepted. +This was present in old design, but not implemented safely. + +#### Old design: + +``` +VariableServerSessionThread + + wait_for_accept() + - bool connection_accepted = false +``` + +`wait_for_accept` just busy waits on `connection_accepted` with no synch constructs (John Borland would be horrified) +VariableServerListenThread calls `VsThread.wait_for_accept()` + +No error handling if connection failed, which can result in hanging listen thread and extra VSThreads bc of other problems in listen thread + +#### New Design + +``` +VariableServerSessionThread + + enum ConnectionStatus { CONNECTION_PENDING, CONNECTION_SUCCESS, CONNECTION_FAIL }; + + wait_for_accept() + + - ConnectionStatus _connection_status = CONNECTION_PENDING + - pthread_mutex_t _connection_status_mutex + - pthread_cond_t _connection_status_cv + + +VariableServerSessionThread::thread_body + Try to accept connection + if connection fails: + with _connection_status_mutex: + set _connection_status to CONNECTION_FAIL + signal on _connection_status_cv + exit thread + + Do some other setup + with _connection_status_mutex: + set _connection_status to CONNECTION_SUCCESS + signal on _connection_status_cv + + Run loop + + +VariableServerSessionThread::wait_for_accept + with _connection_status_mutex: + while _connection_status == CONNECTION_PENDING: + wait on _connection_status_cv + + return _connection_status + + +VariableServerListenThread::thread_body + When we're trying to set up a connection: + Create new VSThread, set up the connection, start the thread + status = VSThread->wait_for_accept() + if status is CONNECTION_FAIL: + make sure the thread has exited and delete it + +``` + +### VariableServerSessionThread and VariableServerListenThread no longer use pthread_cancel +New termination design: +``` +Additions to Threadbase + + test_shutdown(/* optional exit handler args */) + + thread_shutdown(/* optional exit handler args */) + + - bool cancellable = true + - pthread_mutex_t shutdown_mutex + - bool should_shutdown = false + +ThreadBase::test_shutdown: + with shutdown_mutex + if should_shutdown: + call thread_shutdown + +ThreadBase::thread_shutdown + Call exit handler if one passed + Call pthread_exit(0) + +Addition to function of Threadbase::cancel_thread(): + with shutdown_mutex: Set should_shutdown to true + if cancellable == true, call pthread_cancel +``` + +Threads can set cancellable to false in their constructor, which VariableServerSessionThread and VariableServerListenThread do +Instead, they call test_shutdown at points that would be appropriate to shutdown in their loops +Careful design consideration and testing was taken so that if the threads never start, or various error cases are hit, this still goes ok + + +### VariableServerSessionThread and VariableServerListenThread pause on checkpoint reload + +#### Old design: + +``` +VariableServerSessionThread and VariableServerListenThread + - pthread_mutex_t restart_pause +``` + +This mutex is acquired by the top of the loops while they do their main processing, and released at the bottom of the loop. + +The main thread acquires the mutex in preload_checkpoint jobs and releases in restart jobs. + +This created a few bugs: +- a deadlock in VariableServerSessionThread due to a lock inversion with the VariableServer map mutex +- For some reason, holding the lock while blocked on the select syscall by the VariableServerListenThread could cause some really weird bugs, so the lock was acquired after the select call and only if it returned true. Because of this, select could be run while checkpoint restart was happening, which sometimes resulted in false positives, which created extra VariableServerSessionThreads, and the connection status was never checked so they would never shut down and cause all kinds of problems. + +#### New design + +``` +Addition to SysThread + + void force_thread_to_pause() + + void unpause_thread() + + void test_pause() + + - pthread_mutex_t _restart_pause_mutex + - bool _thread_should_pause + - pthread_cond_t _thread_wakeup_cv + - bool _thread_has_paused + - pthread_cond_t _thread_has_paused_cv +``` + +This design still ensures that thread can be paused, and that the main thread will wait until the threads are paused. It uses condvars to solve the deadlock problem and doesn't cause bugs observed in the old design. + +The main thread can control the SysThread by calling `thread->force_thead_to_pause()`, which will block until the SysThread has paused, and `thread->unpause_thread()`. The SysThread loops must call `test_pause()` at points that would be appropriate to pause. + +SysThread uses a monitor pattern internally to implement this behavior. Pseudocode for the pause managment: + +``` +// Called from main thread +SysThread::force_thread_to_pause() + with _restart_pause_mutex: + _thread_should_pause = true + while !_thread_has_paused: + wait on _thread_has_paused_cv + +// Called from main thread +SysThread::unpause_thread(): + with _restart_pause_mutex: + _thread_should_pause = false + signal on _thread_wakeup_cv + +// Called from SysThread +SysThread::test_pause(): + with _restart_pause_mutex: + if _thread_should_pause: + _thread_has_paused = true + signal on _thread_has_paused_cv + + while _thread_should_pause + wait on _thread_wakeup_cv + + _thread_has_paused = false +``` + +## VariableReference + +The VariableReference class existed in the old version only to hold buffers for copied values, it had no methods. + +In the refactored version, the VariableReference class encapsulates all interaction with the memory manager and sim variables, including copying and formatting the values. VariableReference keeps track of the units that have been requested for this variable in this session. VariableReference provides an interface to write out the value of a variable in either Ascii or binary format. + +It still uses REF2 internally, but the encapsulation will make the (hopefully) impending transition to something else easier to manage. + +The VariableReference class is covered by unit tests. + +``` +VariableReference Interface + + VariableReference(std::string var_name) + + + std::string getName() + + TRICK_TYPE getType() + + + std::string getBaseUnits() + + int setRequestedUnits(std::string new_units) + + + int stageValue () + + int prepareForWrite () + + bool isStaged () + + bool isWriteReady () + + + int writeValueAscii( std::ostream& out ) + + int writeValueBinary( std::ostream& out , bool byteswap = false) + + ((Other methods to write out information about the binary or ascii formats)) +``` + +## VariableServerSession + +The VariableServerSession class is a new class created in this refactor. A lot of the behavior of the old VariableServerSessionThread class is now in this class. + +A VariableServerSession represents a single client session. It tracks the list of variables that are being sent, the copy and write mode, output format mode, cycle, pause state, exit command. + +The VariableServerSession has methods that should be called from Trick job types to implement correct moding - + +``` +// Called from different types of Trick jobs. +// Determines whether this session should be copying and/or writing, and performs those actions if so ++ int copy_and_write_freeze(long long curr_frame); ++ int copy_and_write_freeze_scheduled(long long curr_tics); ++ int copy_and_write_scheduled(long long curr_tics); ++ int copy_and_write_top(long long curr_frame); + +// Called from VariableServerSessionThread ++ virtual int copy_and_write_async(); +``` + +The VariableServerSession has a method that should be called when it is time to handle a message and act on it. This is called from the VariableServerSessionThread loop. +``` ++ virtual int handle_message(); +``` + +The VariableServerSession has the entirety of the documented VariableServer API as methods as well. These are called from the single VariableServer object after looking up the session. + + +The VariableServerSession class is covered by unit tests. + + +## VariableServerSessionThread + +The VariableServerSessionThread class runs and manages the lifetime and synchronization of a VariableServerSessionThread, sets up and closes the connection for the VariableServerSession, and calls any VariableServerSession methods that are designed to be asynchronous with the main thread. This includes `handle_message` and `copy_and_write_async`. It also creates the cycle of the session. + +``` +VariableServerSessionThread_loop.cpp + - void thread_body() + +VariableServerSessionThread.cpp + + void preload_checkpoint() + + void restart() + + + ConnectionStatus wait_for_accept() +``` + +`thread_body()` runs the main loop of the VST. + +`preload_checkpoint()` and `restart()` are called from their respective Trick jobs in the main thread. + + +## VariableServer + +The VariableServer class itself has been the least changed by this refactor. + +Its main job is to delegate between the Trick jobs and the various sessions and threads. The VariableServer is a singleton that is declared as part of a SimObject in `default_trick_sys.sm` +``` +VariableServerSimObject { + VariableServer vs; + + {TRK} ("default_data") vs.default_data() ; + {TRK} P0 ("initialization") trick_ret = vs.init() ; + {TRK} ("preload_checkpoint") vs.suspendPreCheckpointReload(); + {TRK} ("restart") vs.restart(); + {TRK} ("restart") vs.resumePostCheckpointReload(); + {TRK} ("top_of_frame") vs.copy_and_write_top() ; + {TRK} ("automatic_last") vs.copy_and_write_scheduled() ; + + {TRK} ("freeze_init") vs.freeze_init() ; + {TRK} ("freeze_automatic") vs.copy_and_write_freeze_scheduled() ; + {TRK} ("freeze") vs.copy_and_write_freeze() ; + + {TRK} ("shutdown") vs.shutdown() ; +} +``` + +In each job: +- default_data: Initialize the listening socket with any port +- initialization: If the user has requested a different port, set that up. Start the listening thead. +- preload_checkpoint: call methods of all VariableServerListenThreads and VariableServerSessionThreads to pause in preparation for checkpoint +- restart: recreate listening threads if necessary, and call restart methods of all VariableServerListenThreads and VariableServerSessionThreads +- top_of_frame, automatic_last, freeze, freeze_automatic: Loop through all VariableServerSessions and call the appropriate copy_data_ variant +- shutdown: cancel all VariableServerListenThreads and VariableServerSessionThreads + +The VariableServer implements part of the dataflow for commands that come in through the input processor. + +The VariableServer's external API is the entry point of the variable server commands that are passed though the input processor. These commands go to a C-style API, which query the static global singleton VariableServer object. Each of these commands will look up the correct session by pthread_id and call the session's command. + +``` +Client sends `"trick.var_add("my_variable")\n"` +`VariableServerSessionThread::thread_body` calls `VariableServerSession::handle_mesage` +`VariableServerSession::handle_mesage` reads from the client connection and calls `ip_parse("trick.var_add("my_variable")\n")` +Python interpreter runs (on the variable server thread) back through the SWIG layer to invoke global method `var_add("my_variable")` in var_server_ext.cpp +`var_add()` calls the static global variable server object to look up the current session, mapped to the current pthread_id +`var_add()` calls `session->var_add("my_variable")` +session will add my_variable to its list of variables +Return from `ip_parse`, continue `thread_body` processing +``` + +Code snippets (some minor modifications for clarity): + +``` +// var_server_ext.cpp +int var_add(std::string in_name) { + Trick::VariableServerSession * session = get_session(); + + if (session != NULL ) { + session->var_add(in_name) ; + } + return(0) ; +} + +Trick::VariableServerSession * get_session() { + return the_vs->get_session(pthread_self()) ; +} + +// VariableServer.cpp +Trick::VariableServerSession * Trick::VariableServer::get_session(pthread_t thread_id) { + Trick::VariableServerSession * ret = NULL ; + pthread_mutex_lock(&map_mutex) ; + auto it = var_server_sessions.find(thread_id) ; + if ( it != var_server_sessions.end() ) { + ret = (*it).second ; + } + pthread_mutex_unlock(&map_mutex) ; + return ret ; +} + +// VariableServerSession_commands.cpp +int Trick::VariableServerSession::var_add(std::string in_name) { + + VariableReference new_var = new VariableReference(in_name); + _session_variables.push_back(new_var) ; + + return(0) ; +} +``` + +![var_add Sequence Diagram](images/var_add_sequence.png) + +Both var_server_ext.cpp and VariableServerSession_commands.cpp have a method for each command in the user facing variable server API. + +### Special case listen threads and sessions + +The user can request addition TCP, UDP, or multicast servers from the input file. This is handled in the VariableServer class. + +``` +VariableServer { + + int create_tcp_socket(const char * address, unsigned short in_port ) + + int create_udp_socket(const char * address, unsigned short in_port ) + + int create_multicast_socket(const char * mcast_address, const char * address, unsigned short in_port ) +} +``` + +Calling `create_tcp_socket` will create an additional listen thread, which will operate in the same way as the default listen thread. The VariableServer will create a TCPClientListener and start the listen thread, just like it does for the default listen thread. + +`create_udp_socket` will open a UDP socket. Since it does not require a listener, the VariableServer creates a VariableServerSessionThread and a UDPConnection directly, and starts the thread. The output for this session is sent to whatever host a message is received from. + +`create_multicast_socket` will create a UDP socket and add it to a multicast group. It will also create and start a VariableServerSessionThread and MulticastGroup object. The output for this session is broadcast on the multicast address given. + +## ClientConnection and TCPClientListener + +The purpose of these classes are to abstract away all of the network code and the differences between TCP, UDP, and multicast connections for the purpose of the VariableServerSession. + +The VariableServerListenThread uses a TCPClientListener to listen for connections on an open socket, and when one comes in, the TCPClientListener will create a TCPConnection to service the connection. + +In the UDP and Multicast case, the connection is created directly in the VariableServer class and passed into the VariableServerSessionThread. + + +## Class Diagram + +![Variable Server UML](images/vs_uml.png) + diff --git a/docs/developer_docs/images/var_add_sequence.png b/docs/developer_docs/images/var_add_sequence.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec90ae263e916ae6aaa4518858647043c92b89d GIT binary patch literal 77896 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 literal 0 HcmV?d00001 diff --git a/docs/developer_docs/images/vs_uml.png b/docs/developer_docs/images/vs_uml.png new file mode 100644 index 0000000000000000000000000000000000000000..f44a799f20beb3ccb380d310d3f1eaf70ef752ab GIT binary patch literal 243270 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 QFuob`GRo5VQcqv~AKlt}_W%F@ literal 0 HcmV?d00001 diff --git a/docs/documentation/simulation_capabilities/Threads.md b/docs/documentation/simulation_capabilities/Threads.md index c032840b..7fb3daf5 100644 --- a/docs/documentation/simulation_capabilities/Threads.md +++ b/docs/documentation/simulation_capabilities/Threads.md @@ -12,7 +12,7 @@ A trick sim is a multi-threaded process, and all of the threads that are created | MessageTCDeviceListenThread | `trick_message.mdevice.get_listen_thread()` | | MessageThreadedCout | `trick_message.mtcout` | | DRDWriterThread | `trick_data_record.drd.drd_writer_thread` | -| VariableServerThread | `trick_vs.vs.get_vst(pthread_t thread_id)` | +| VariableServerSessionThread | `trick_vs.vs.get_vst(pthread_t thread_id)` | ## ThreadBase Methods diff --git a/include/trick/ClientConnection.hh b/include/trick/ClientConnection.hh index 064a1e4a..2eb5dcf7 100644 --- a/include/trick/ClientConnection.hh +++ b/include/trick/ClientConnection.hh @@ -9,11 +9,13 @@ LIBRARY DEPENDENCIES: #define CLIENT_CONNECTION_HH #include -// #include namespace Trick { class ClientConnection { public: + + virtual ~ClientConnection() { } + static const unsigned int MAX_CMD_LEN = 200000 ; enum ConnectionType { TCP, UDP, MCAST, WS } ; @@ -24,17 +26,20 @@ namespace Trick { virtual int write (const std::string& message) = 0; virtual int write (char * message, int size) = 0; - virtual std::string read (int max_len = MAX_CMD_LEN) = 0; + virtual int read (std::string& message, int max_len = MAX_CMD_LEN) = 0; virtual int setBlockMode (bool blocking) = 0; virtual int disconnect () = 0; virtual bool isInitialized() = 0; - virtual std::string getClientTag (); - virtual int setClientTag (std::string tag); + virtual std::string getClientTag () = 0; + virtual int setClientTag (std::string tag) = 0; - virtual int restart() {}; + virtual int restart() = 0; + + virtual std::string getClientHostname() = 0; + virtual int getClientPort() = 0; protected: ConnectionType _connection_type; diff --git a/include/trick/ClientListener.hh b/include/trick/ClientListener.hh deleted file mode 100644 index ebba212f..00000000 --- a/include/trick/ClientListener.hh +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef CLIENT_LISTENER_HH -#define CLIENT_LISTENER_HH - -/* - PURPOSE: ( Encapsulate a TCP server. ) -*/ - -#include -#include "trick/SystemInterface.hh" -#include "trick/TCPConnection.hh" - -#define LISTENER_ERROR -1 - - -namespace Trick { - - class TCPConnection; - - class ClientListener { - public: - - ClientListener (); - ClientListener (SystemInterface * system_interface); - - ~ClientListener (); - - int initialize(std::string hostname, int port); - int initialize(); - - int setBlockMode(bool blocking); - - bool checkForNewConnections(); - - TCPConnection * setUpNewConnection (); - - std::string getHostname (); - - int getPort(); - - int disconnect(); - - int checkSocket(); - - bool validateSourceAddress(std::string source_address); - - bool isInitialized(); - - int restart(); - - private: - - int _listen_socket; - std::string _hostname; - int _port; - std::string _client_tag; - - bool _initialized; - - SystemInterface * _system_interface; /* ** */ - }; -} - -#endif \ No newline at end of file diff --git a/include/trick/MulticastGroup.hh b/include/trick/MulticastGroup.hh new file mode 100644 index 00000000..c6095b3e --- /dev/null +++ b/include/trick/MulticastGroup.hh @@ -0,0 +1,55 @@ +/* + PURPOSE: ( Encapsulate multicast functionality. ) +*/ +#ifndef MULTICAST_GROUP_HH +#define MULTICAST_GROUP_HH + +#include +#include +#include + +#include +#include + +namespace Trick { + class MulticastGroup : public UDPConnection { + public: + MulticastGroup(); + MulticastGroup (SystemInterface * system_interface); + + virtual ~MulticastGroup(); + + // Multicast specific functions + int initialize_with_receiving(std::string local_addr, std::string mcast_addr, int port); + virtual int initialize(); + + virtual int broadcast (std::string message); + virtual int addAddress (std::string addr, int port); + + // ClientConnection interface + + virtual int write (const std::string& message) override; + virtual int write (char * message, int size) override; + + virtual int read (std::string& message, int max_len = MAX_CMD_LEN) override; + + virtual int disconnect () override; + + virtual bool isInitialized() override; + + virtual int restart() override; + + virtual std::string getClientHostname() override; + virtual int getClientPort() override; + + private: + std::vector _addresses; /**< trick_io(**) Addresses to multicast to. */ + bool _initialized; /**< trick_io(**) Whether this object is ready */ + + struct sockaddr_in _self_info ; /**< trick_io(**) Save self information so we don't process our own messages */ + + SystemInterface * _system_interface; + }; +} + +#endif \ No newline at end of file diff --git a/include/trick/MulticastManager.hh b/include/trick/MulticastManager.hh deleted file mode 100644 index a69da6b2..00000000 --- a/include/trick/MulticastManager.hh +++ /dev/null @@ -1,28 +0,0 @@ -/* - PURPOSE: ( Encapsulate multicast functionality. ) -*/ - -#include -#include -#include - -namespace Trick { - class MulticastManager { - public: - MulticastManager(); - ~MulticastManager(); - - int broadcast (std::string message); - int addAddress (std::string addr, int port); - int restart (); - int initialize(); - int is_initialized(); - - - private: - std::vector addresses; /**< trick_io(**) Addresses to multicast to. */ - int mcast_socket; /**< trick_io(**) Socket opened in initialization. */ - int initialized; /**< trick_io(**) Whether manager is ready */ - - }; -} \ No newline at end of file diff --git a/include/trick/SysThread.hh b/include/trick/SysThread.hh index d5f639dd..e5aab8e4 100644 --- a/include/trick/SysThread.hh +++ b/include/trick/SysThread.hh @@ -38,7 +38,29 @@ namespace Trick { static int ensureAllShutdown(); + protected: + // Called from the main thread + void force_thread_to_pause(); + // Called from the main thread + void unpause_thread(); + + // Called from the sys_thread at a point that would be appropriate to pause + void test_pause(); + private: + /** Synchronization to safely pause and restart processing during a checkpoint reload */ + pthread_mutex_t _restart_pause_mutex ; /**< trick_io(**) */ + + // For the main thread to tell the sys_thread to pause + bool _thread_should_pause; /**< trick_io(**) */ + // For the main thread to tell the sys_thread to wake up + pthread_cond_t _thread_wakeup_cv; /**< trick_io(**) */ + + // For the main thread to wait for the sys_thread to pause + pthread_cond_t _thread_has_paused_cv; /**< trick_io(**) */ + bool _thread_has_paused; /**< trick_io(**) */ + + // Had to use Construct On First Use here to avoid the static initialziation fiasco static pthread_mutex_t& list_mutex(); static pthread_cond_t& list_empty_cv(); diff --git a/include/trick/SystemInterface.hh b/include/trick/SystemInterface.hh index b6daa767..c13dbb92 100644 --- a/include/trick/SystemInterface.hh +++ b/include/trick/SystemInterface.hh @@ -11,6 +11,7 @@ #include #include #include +#include class SystemInterface { public: @@ -19,7 +20,7 @@ class SystemInterface { virtual int setsockopt (int socket, int level, int option_name, const void * option_value, socklen_t option_len) { return ::setsockopt ( socket, level, option_name, option_value, option_len); } - virtual int bind (int socket, struct sockaddr * address, socklen_t address_len) { return ::bind ( socket, address, address_len); } + virtual int bind (int socket, const struct sockaddr * address, socklen_t address_len) { return ::bind ( socket, address, address_len); } virtual int getsockname (int socket, struct sockaddr * address, socklen_t * address_len) { return ::getsockname ( socket, address, address_len); } @@ -45,6 +46,8 @@ class SystemInterface { virtual ssize_t recvfrom (int socket, void * buffer, size_t length, int flags, struct sockaddr * address, socklen_t * address_len) { return ::recvfrom ( socket, buffer, length, flags, address, address_len); } + virtual in_addr_t inet_addr (const char * cp) { return ::inet_addr ( cp); } + }; #endif diff --git a/include/trick/TCPClientListener.hh b/include/trick/TCPClientListener.hh new file mode 100644 index 00000000..dd5435d3 --- /dev/null +++ b/include/trick/TCPClientListener.hh @@ -0,0 +1,62 @@ +#ifndef CLIENT_LISTENER_HH +#define CLIENT_LISTENER_HH + +/* + PURPOSE: ( Encapsulate a TCP server. ) +*/ + +#include +#include "trick/SystemInterface.hh" +#include "trick/TCPConnection.hh" + +#define LISTENER_ERROR -1 + + +namespace Trick { + + class TCPConnection; + + class TCPClientListener { + public: + + TCPClientListener (); + TCPClientListener (SystemInterface * system_interface); + + virtual ~TCPClientListener (); + + virtual int initialize(std::string hostname, int port); + virtual int initialize(); + + virtual int setBlockMode(bool blocking); + + virtual bool checkForNewConnections(); + + virtual TCPConnection * setUpNewConnection (); + + virtual int disconnect(); + + virtual int checkSocket(); + + virtual bool validateSourceAddress(std::string source_address); + + virtual bool isInitialized(); + + virtual int restart(); + + virtual std::string getHostname (); + virtual int getPort(); + + protected: + + int _listen_socket; + std::string _hostname; + int _port; + std::string _client_tag; + + bool _initialized; + + SystemInterface * _system_interface; /* ** */ + }; +} + +#endif \ No newline at end of file diff --git a/include/trick/TCPConnection.hh b/include/trick/TCPConnection.hh index 9285610d..6b34dedd 100644 --- a/include/trick/TCPConnection.hh +++ b/include/trick/TCPConnection.hh @@ -13,23 +13,30 @@ namespace Trick { class TCPConnection : public ClientConnection { public: + TCPConnection (); TCPConnection (int listen_socket); TCPConnection (SystemInterface * system_interface); TCPConnection (int listen_socket, SystemInterface * system_interface); - int start() override; + virtual int start() override; - int write (const std::string& message) override; - int write (char * message, int size) override; + virtual int write (const std::string& message) override; + virtual int write (char * message, int size) override; - std::string read (int max_len = MAX_CMD_LEN) override; + virtual int read (std::string& message, int max_len = MAX_CMD_LEN) override; - int disconnect () override; - bool isInitialized() override; + virtual int disconnect () override; + virtual bool isInitialized() override; - int setBlockMode(bool blocking) override; + virtual int setBlockMode(bool blocking) override; - int restart() override; + virtual int restart() override; + + virtual std::string getClientTag () override; + virtual int setClientTag (std::string tag) override; + + virtual std::string getClientHostname() override; + virtual int getClientPort() override; private: int _socket; diff --git a/include/trick/UDPConnection.hh b/include/trick/UDPConnection.hh index abfc7e6a..c64539d3 100644 --- a/include/trick/UDPConnection.hh +++ b/include/trick/UDPConnection.hh @@ -22,7 +22,7 @@ namespace Trick { int write (const std::string& message) override; int write (char * message, int size) override; - std::string read (int max_len = MAX_CMD_LEN) override; + int read (std::string& message, int max_len = MAX_CMD_LEN) override; int disconnect () override; bool isInitialized() override; @@ -30,15 +30,19 @@ namespace Trick { int setBlockMode(bool block_mode) override; int restart() override; + virtual std::string getClientTag () override; + virtual int setClientTag (std::string tag) override; + // Non-override functions int initialize(const std::string& hostname, int port); - int getPort(); std::string getHostname(); - - private: + virtual std::string getClientHostname() override; + virtual int getClientPort() override; + + protected: bool _initialized; bool _started; int _socket; diff --git a/include/trick/VariableReference.hh b/include/trick/VariableReference.hh index 6e97b217..359a300a 100644 --- a/include/trick/VariableReference.hh +++ b/include/trick/VariableReference.hh @@ -27,17 +27,10 @@ namespace Trick { ~VariableReference(); - const char* getName() const; + std::string getName() const; TRICK_TYPE getType() const; - // There are 2 different "units" variables used - REF2->units and REF2->attr->units - // REF2->attr->units should not be changed, it is what the variable is represented in internally - // REF2->units is the unit that has been requested by the variable server client - // REF2->units should be null unless var_units or var_add with units is called - // We'll refer to REF2->attr->units as BaseUnits and REF2->units as RequestedUnits - // Encapsulate all of this away so that no one else has to think about ref->attr->units vs ref->units - // ^ TODO: this is not where the above documentation should be. put it somewhere else. - const char* getBaseUnits() const; + std::string getBaseUnits() const; int setRequestedUnits(std::string new_units); // These variables have 2 staging buffers that we can swap between to allow for different copy and write-out modes @@ -66,8 +59,6 @@ namespace Trick { // Helper method for byteswapping static void byteswap_var (char * out, char * in, const VariableReference& ref); - // TODO: Some system for error messaging - private: VariableReference(); void byteswap_var(char * out, char * in) const; @@ -76,22 +67,25 @@ namespace Trick { static REF2* make_error_ref(std::string in_name); static REF2* make_do_not_resolve_ref(std::string in_name); - static int bad_ref_int; - static int do_not_resolve_bad_ref_int; + static int _bad_ref_int; + static int _do_not_resolve_bad_ref_int; - REF2 *var_info; - void *address; // -- address of data copied to buffer - int size; // -- size of data copied to buffer - bool deref; // -- indicates whether variable is pointer that needs to be dereferenced - cv_converter * conversion_factor ; // ** udunits conversion factor - TRICK_TYPE trick_type ; // -- Trick type of this variable + REF2 * _var_info; + void * _address; // -- address of data copied to buffer + int _size; // -- size of data copied to buffer + bool _deref; // -- indicates whether variable is pointer that needs to be dereferenced + cv_converter * _conversion_factor ; // ** udunits conversion factor + TRICK_TYPE _trick_type ; // -- Trick type of this variable - // TODO: use atomics for these - bool staged; - bool write_ready; + bool _staged; + bool _write_ready; - void *stage_buffer; - void *write_buffer; + void *_stage_buffer; + void *_write_buffer; + + std::string _base_units; + std::string _requested_units; + std::string _name; }; std::ostream& operator<< (std::ostream& s, const Trick::VariableReference& ref); diff --git a/include/trick/VariableServer.hh b/include/trick/VariableServer.hh index eeff4b6a..87cd1349 100644 --- a/include/trick/VariableServer.hh +++ b/include/trick/VariableServer.hh @@ -16,7 +16,7 @@ #include "trick/reference.h" #include "trick/JobData.hh" #include "trick/variable_server_sync_types.h" -#include "trick/VariableServerThread.hh" +#include "trick/VariableServerSessionThread.hh" #include "trick/VariableServerListenThread.hh" #include "trick/SysThread.hh" @@ -85,27 +85,27 @@ namespace Trick { /** @brief Copies client variable values at the top of the frame. */ - int copy_data_top() ; + int copy_and_write_top() ; /** @brief The function to copy client variable values to their output buffers when in sync mode. */ - int copy_data_scheduled() ; + int copy_and_write_scheduled() ; /** @brief The function to copy client variable values to their output buffers when in sync mode. */ - int copy_data_freeze_scheduled() ; + int copy_and_write_freeze_scheduled() ; /** @brief Copies client variable values at the top of the frame. */ - int copy_data_freeze() ; + int copy_and_write_freeze() ; /** @brief Adds a vst to the map. */ - void add_vst(pthread_t thread_id, VariableServerThread * in_vst ) ; + void add_vst(pthread_t thread_id, VariableServerSessionThread * in_vst ) ; /** @brief Adds a vst to the map. @@ -114,9 +114,9 @@ namespace Trick { /** @brief Get a vst mapped by thread id - @return the VariableServerThread mapped to the thread id if found, or NULL if not found. + @return the VariableServerSessionThread mapped to the thread id if found, or NULL if not found. */ - Trick::VariableServerThread * get_vst(pthread_t thread_id) ; + Trick::VariableServerSessionThread * get_vst(pthread_t thread_id) ; /** @brief Get a session mapped by thread id @@ -266,9 +266,9 @@ namespace Trick { void set_copy_data_job( Trick::JobData * ) ; /** - @brief Called from the S_define to set the copy_data_freeze_job ptr. + @brief Called from the S_define to set the copy_and_write_freeze_job ptr. */ - void set_copy_data_freeze_job( Trick::JobData * ) ; + void set_copy_and_write_freeze_job( Trick::JobData * ) ; protected: @@ -289,16 +289,10 @@ namespace Trick { Trick::JobData * copy_data_job ; /**< trick_io(**) trick_units(--) */ /** Pointer to freeze_automatic job that copies requested variable values to their output buffers in sync mode.\n */ - Trick::JobData * copy_data_freeze_job ; /**< trick_io(**) trick_units(--) */ + Trick::JobData * copy_and_write_freeze_job ; /**< trick_io(**) trick_units(--) */ - /** Storage for saved thread pause state. The pause state of each thread is saved - to this map by suspendPreCheckpointReload(). resumePostCheckpointReload() restores - the pause state from this map. - */ - std::map thread_pause_state_store; // ** ignore this - - /** Map thread id to the VariableServerThread object.\n */ - std::map < pthread_t , VariableServerThread * > var_server_threads ; /**< trick_io(**) */ + /** Map thread id to the VariableServerSessionThread object.\n */ + std::map < pthread_t , VariableServerSessionThread * > var_server_threads ; /**< trick_io(**) */ std::map < pthread_t , VariableServerSession * > var_server_sessions ; /**< trick_io(**) */ /** Mutex to ensure only one thread manipulates the map of var_server_threads\n */ diff --git a/include/trick/VariableServerListenThread.hh b/include/trick/VariableServerListenThread.hh index bc87a34e..3f4f6d26 100644 --- a/include/trick/VariableServerListenThread.hh +++ b/include/trick/VariableServerListenThread.hh @@ -8,10 +8,9 @@ #include #include -// #include "trick/tc.h" -#include "trick/ClientListener.hh" +#include "trick/TCPClientListener.hh" #include "trick/SysThread.hh" -#include "trick/MulticastManager.hh" +#include "trick/MulticastGroup.hh" namespace Trick { @@ -19,11 +18,14 @@ namespace Trick { /** This class runs the variable server listen loop. @author Alex Lin + @author Jackie Deans (2023) */ class VariableServerListenThread : public Trick::SysThread { public: VariableServerListenThread() ; + VariableServerListenThread(TCPClientListener * listener); + virtual ~VariableServerListenThread() ; const char * get_hostname() ; @@ -52,7 +54,7 @@ namespace Trick { void pause_listening() ; void restart_listening() ; - void create_tcp_socket(const char * address, unsigned short in_port ) ; + void set_multicast_group (MulticastGroup * group); virtual void dump( std::ostream & oss = std::cout ) ; @@ -60,28 +62,25 @@ namespace Trick { void initializeMulticast(); /** Requested variable server source address\n */ - std::string requested_source_address ; /**< trick_units(--) */ + std::string _requested_source_address ; /**< trick_units(--) */ /** Requested variable server port number.\n */ - unsigned short requested_port ; /**< trick_units(--) */ + unsigned short _requested_port ; /**< trick_units(--) */ /** User requested specific port number\n */ - bool user_requested_address ; /**< trick_units(--) */ + bool _user_requested_address ; /**< trick_units(--) */ /** User defined unique tag to easily identify this variable server port.\n */ - std::string user_tag; /**< trick_units(--) */ + std::string _user_tag; /**< trick_units(--) */ /** Turn on/off broadcasting of variable server port.\n */ - bool broadcast ; /**< trick_units(--) */ + bool _broadcast ; /**< trick_units(--) */ /** The listen device */ - ClientListener listener; /**< trick_io(**) trick_units(--) */ + TCPClientListener * _listener; /**< trick_io(**) trick_units(--) */ /* Multicast broadcaster */ - MulticastManager multicast; /**< trick_io(**) trick_units(--) */ - - /** The mutex to stop accepting new connections during restart\n */ - pthread_mutex_t restart_pause ; /**< trick_io(**) */ + MulticastGroup * _multicast; /**< trick_io(**) trick_units(--) */ } ; diff --git a/include/trick/VariableServerSession.hh b/include/trick/VariableServerSession.hh index 00a05704..97f169a5 100644 --- a/include/trick/VariableServerSession.hh +++ b/include/trick/VariableServerSession.hh @@ -2,8 +2,8 @@ PURPOSE: (Represent the state of a variable server connection.) **************************************************************************/ -#ifndef WSSESSION_HH -#define WSSESSION_HH +#ifndef VSSESSION_HH +#define VSSESSION_HH #include #include @@ -18,12 +18,99 @@ PURPOSE: (Represent the state of a variable server connection.) namespace Trick { class VariableServerSession { public: - VariableServerSession(ClientConnection * connection); - ~VariableServerSession(); + VariableServerSession(); + + virtual ~VariableServerSession(); friend std::ostream& operator<< (std::ostream& s, const Trick::VariableServerSession& session); - int handleMessage(); + /** + * @brief Set the connection object + * + * @param connection - a pointer to a ClientConnection that is initialized and ready to go + */ + virtual void set_connection(ClientConnection * connection); + + /** + @brief Read a message from the connection and act on it + */ + virtual int handle_message(); + + /** + @brief Get the pause state of this thread. + */ + virtual bool get_pause() ; + + /** + @brief Set the pause state of this thread. + */ + virtual void set_pause(bool on_off) ; + + /** + @brief Get the exit command of this session. + */ + virtual bool get_exit_cmd() ; + + /** + @brief Command session to exit + */ + virtual void set_exit_cmd() ; + + /** + @brief gets the send_stdio flag. + */ + virtual bool get_send_stdio() ; + + /** + @brief sets the send_stdio flag. + */ + virtual int set_send_stdio(bool on_off) ; + + // Called from different types of Trick jobs. + // Determines whether this session should be copying at that time, and calls internal copy methods if so + int copy_and_write_freeze(long long curr_frame); + int copy_and_write_freeze_scheduled(long long curr_tics); + int copy_and_write_scheduled(long long curr_tics); + int copy_and_write_top(long long curr_frame); + + // Called from VariableServerSessionThread + virtual int copy_and_write_async(); + + /** + @brief Copy given variable values from Trick memory to each variable's output buffer. + cyclical indicated whether it is a normal cyclical copy or a send_once copy + */ + virtual int copy_sim_data(std::vector& given_vars, bool cyclical); + virtual int copy_sim_data(); + + /** + @brief Write data from the given var only to the appropriate format (var_ascii or var_binary) from variable output buffers to socket. + */ + virtual int write_data(std::vector& var, VS_MESSAGE_TYPE message_type) ; + virtual int write_data(); + + int write_stdio(int stream, std::string text); + + void disconnect_references(); + + virtual long long get_next_tics() const; + + virtual long long get_freeze_next_tics() const; + + int freeze_init(); + + virtual double get_update_rate() const; + + void pause_copy(); + void unpause_copy(); + + virtual VS_WRITE_MODE get_write_mode () const; + virtual VS_COPY_MODE get_copy_mode () const; + + + /************************************************************************************************/ + /* Variable Server Interface */ + /************************************************************************************************/ /** @brief @userdesc Command to add a variable to a list of registered variables for value retrieval. @@ -34,7 +121,7 @@ namespace Trick { @param in_name - the variable name to retrieve @return always 0 */ - int var_add( std::string in_name ) ; + virtual int var_add( std::string in_name ) ; /** @brief @userdesc Command to add a variable to a list of registered variables for value retrieval, @@ -50,7 +137,7 @@ namespace Trick { @param units_name - the desired units, specified within curly braces @return always 0 */ - int var_add( std::string in_name, std::string units_name ) ; + virtual int var_add( std::string in_name, std::string units_name ) ; /** @brief @userdesc Command to remove a variable (previously registered with var_add) @@ -60,7 +147,7 @@ namespace Trick { @param in_name - the variable name to remove @return always 0 */ - int var_remove( std::string in_name ) ; + virtual int var_remove( std::string in_name ) ; /** @brief @userdesc Command to instruct the variable server to return the value of a variable @@ -75,7 +162,7 @@ namespace Trick { @note trick.var_add("my_object.my_variable"); trick.var_units("my_object.my_variable","{m}") is the same as trick.var_add("my_object.my_variable","{m}") */ - int var_units(std::string var_name , std::string units_name) ; + virtual int var_units(std::string var_name , std::string units_name) ; /** @brief @userdesc Command to instruct the variable server to send a Boolean value indicating @@ -89,7 +176,7 @@ namespace Trick { @param in_name - the variable name to query @return always 0 */ - int var_exists( std::string in_name ) ; + virtual int var_exists( std::string in_name ) ; /** @brief @userdesc Command to immediately send the value of a comma separated list of variables @@ -99,7 +186,7 @@ namespace Trick { @param num_vars - number of vars in in_name_list @return always 0 */ - int var_send_once(std::string in_name_list, int num_vars); + virtual int var_send_once(std::string in_name_list, int num_vars); /** @brief @userdesc Command to instruct the variable server to immediately send back the values of @@ -110,7 +197,7 @@ namespace Trick { @code trick.var_send() @endcode @return always 0 */ - int var_send() ; + virtual int var_send() ; /** @brief @userdesc Command to remove all variables from the list of variables currently @@ -120,7 +207,7 @@ namespace Trick { @code trick.var_clear() @endcode @return always 0 */ - int var_clear() ; + virtual int var_clear() ; /** @brief @userdesc Turns on validating addresses before they are referenced @@ -128,7 +215,7 @@ namespace Trick { @code trick.var_validate_address() @endcode @return always 0 */ - int var_validate_address(bool on_off) ; + virtual int var_validate_address(bool on_off) ; /** @brief @userdesc Command to instruct the variable server to output debug information. @@ -137,7 +224,7 @@ namespace Trick { @return always 0 @param level - 1,2,or 3, higher number increases amount of info output */ - int var_debug(int level) ; + virtual int var_debug(int level) ; /** @brief @userdesc Command to instruct the variable server to return values in ASCII format (this is the default). @@ -145,7 +232,7 @@ namespace Trick { @code trick.var_ascii() @endcode @return always 0 */ - int var_ascii() ; + virtual int var_ascii() ; /** @brief @userdesc Command to instruct the variable server to return values in binary format. @@ -153,7 +240,7 @@ namespace Trick { @code trick.var_binary() @endcode @return always 0 */ - int var_binary() ; + virtual int var_binary() ; /** @brief @userdesc Command to instruct the variable server to return values in binary format, @@ -163,7 +250,7 @@ namespace Trick { @code trick.var_binary_nonames() @endcode @return always 0 */ - int var_binary_nonames() ; + virtual int var_binary_nonames() ; /** @brief @userdesc Command to tell the server when to copy data @@ -175,7 +262,7 @@ namespace Trick { @param mode - One of the above enumerations @return 0 if successful, -1 if error */ - int var_set_copy_mode(int on_off) ; + virtual int var_set_copy_mode(int mode) ; /** @brief @userdesc Command to tell the server when to copy data @@ -186,7 +273,7 @@ namespace Trick { @param mode - One of the above enumerations @return 0 if successful, -1 if error */ - int var_set_write_mode(int on_off) ; + virtual int var_set_write_mode(int mode) ; /** @brief @userdesc Command to put the current variable server/client connection in sync mode, @@ -205,7 +292,7 @@ namespace Trick { 2 = sync data gather, sync socket write @return always 0 */ - int var_sync(int on_off) ; + virtual int var_sync(int on_off) ; /** @brief @userdesc Set the frame multiple @@ -214,7 +301,7 @@ namespace Trick { @param mult - The requested multiple @return 0 */ - int var_set_frame_multiple(unsigned int mult) ; + virtual int var_set_frame_multiple(unsigned int mult) ; /** @brief @userdesc Set the frame offset @@ -223,7 +310,7 @@ namespace Trick { @param offset - The requested offset @return 0 */ - int var_set_frame_offset(unsigned int offset) ; + virtual int var_set_frame_offset(unsigned int offset) ; /** @brief @userdesc Set the frame multiple @@ -232,7 +319,7 @@ namespace Trick { @param mult - The requested multiple @return 0 */ - int var_set_freeze_frame_multiple(unsigned int mult) ; + virtual int var_set_freeze_frame_multiple(unsigned int mult) ; /** @brief @userdesc Set the frame offset @@ -241,7 +328,7 @@ namespace Trick { @param offset - The requested offset @return 0 */ - int var_set_freeze_frame_offset(unsigned int offset) ; + virtual int var_set_freeze_frame_offset(unsigned int offset) ; /** @brief @userdesc Command to instruct the variable server to byteswap the return values @@ -253,7 +340,7 @@ namespace Trick { @param on_off - true (or 1) to byteswap the return data, false (or 0) to return data as is @return always 0 */ - int var_byteswap(bool on_off) ; + virtual int var_byteswap(bool on_off) ; /** @brief @userdesc Command to turn on variable server logged messages to a playback file. @@ -262,7 +349,7 @@ namespace Trick { @code trick.set_log_on() @endcode @return always 0 */ - int set_log_on() ; + virtual int set_log_on() ; /** @brief @userdesc Command to turn off variable server logged messages to a playback file. @@ -270,50 +357,40 @@ namespace Trick { @code trick.set_log_off() @endcode @return always 0 */ - int set_log_off() ; + virtual int set_log_off() ; /** @brief Command to send the number of items in the var_add list. The variable server sends a message indicator of "3", followed by the total number of variables being sent. */ - int send_list_size(); + virtual int send_list_size(); /** @brief Special command to instruct the variable server to send the contents of the S_sie.resource file; used by TV. The variable server sends a message indicator of "2", followed by a tab, followed by the file contents which are then sent as sequential ASCII messages with a maximum size of 4096 bytes each. */ - int send_sie_resource(); + virtual int send_sie_resource(); /** @brief Special command to only send the class sie class information */ - int send_sie_class(); + virtual int send_sie_class(); /** @brief Special command to only send the enumeration sie class information */ - int send_sie_enum(); + virtual int send_sie_enum(); /** @brief Special command to only send the top level objects sie class information */ - int send_sie_top_level_objects(); + virtual int send_sie_top_level_objects(); /** @brief Special command to send an arbitrary file through the variable server. */ - int send_file(std::string file_name); - - /** - @brief gets the send_stdio flag. - */ - bool get_send_stdio() ; - - /** - @brief sets the send_stdio flag. - */ - int set_send_stdio(bool on_off) ; + virtual int send_file(std::string file_name); /** @brief @userdesc Command to set the frequencty at which the variable server will send values @@ -324,157 +401,109 @@ namespace Trick { @param in_cycle - the desired frequency in seconds @return always 0 */ - int var_cycle(double in_cycle) ; + virtual int var_cycle(double in_cycle) ; /** - @brief Get the pause state of this thread. + @brief @userdesc Command exit this variable server session. + @par Python Usage: + @code trick.var_exit() @endcode + @return always 0 */ - bool get_pause() ; - - /** - @brief Set the pause state of this thread. - */ - void set_pause(bool on_off) ; - - /** - @brief Write data in the appropriate format (var_ascii or var_binary) from variable output buffers to socket. - */ - int write_data(); - - /** - @brief Write data from the given var only to the appropriate format (var_ascii or var_binary) from variable output buffers to socket. - */ - int write_data(std::vector& var, VS_MESSAGE_TYPE message_type) ; - - /** - @brief Copy client variable values from Trick memory to each variable's output buffer. - */ - int copy_sim_data(); - - /** - @brief Copy given variable values from Trick memory to each variable's output buffer. - cyclical indicated whether it is a normal cyclical copy or a send_once copy - */ - int copy_sim_data(std::vector& given_vars, bool cyclical); - - int var_exit(); - - int transmit_file(std::string sie_file); - - int copy_data_freeze(); - int copy_data_freeze_scheduled(long long curr_tics); - int copy_data_scheduled(long long curr_tics); - int copy_data_top(); - - // VS_COPY_MODE get_copy_mode(); - // VS_WRITE_MODE get_write_mode(); - - void disconnect_references(); - - long long get_next_tics() const; - - long long get_freeze_next_tics() const; - - int freeze_init(); - - double get_update_rate() const; - - // These should be private probably - int write_binary_data(const std::vector& given_vars, VS_MESSAGE_TYPE message_type); - int write_ascii_data(const std::vector& given_vars, VS_MESSAGE_TYPE message_type ); - int write_stdio(int stream, std::string text); - - VS_WRITE_MODE get_write_mode () const; - VS_COPY_MODE get_copy_mode () const; - - - pthread_mutex_t copy_mutex; /**< trick_io(**) */ - - /** Toggle to indicate var_exit commanded.\n */ - bool exit_cmd ; /**< trick_io(**) */ + virtual int var_exit(); private: - // int sendErrorMessage(const char* fmt, ... ); - // int sendSieMessage(void); - // int sendUnitsMessage(const char* vname); - ClientConnection * connection; /**< trick_io(**) */ - /** The trickcomm device used for the connection to the client.\n */ + pthread_mutex_t _copy_mutex; /**< trick_io(**) */ - VariableReference * find_session_variable(std::string name) const; + ClientConnection * _connection; /**< trick_io(**) */ - double stageTime; - bool dataStaged; + // Helper method to send a file to connection + virtual int transmit_file(std::string sie_file); - std::vector session_variables; /**< trick_io(**) */ - bool cyclicSendEnabled; /**< trick_io(**) */ - long long nextTime; /**< trick_io(**) */ - long long intervalTimeTics; /**< trick_io(**) */ + // Helper methods to write out formatted data + virtual int write_binary_data(const std::vector& given_vars, VS_MESSAGE_TYPE message_type); + virtual int write_ascii_data(const std::vector& given_vars, VS_MESSAGE_TYPE message_type ); + + virtual VariableReference * find_session_variable(std::string name) const; + + std::vector _session_variables; /**< trick_io(**) */ + + // Getters and setters for internal variables + virtual long long get_cycle_tics() const; + + virtual void set_freeze_next_tics(long long tics); + virtual void set_next_tics(long long tics); + + virtual int get_frame_multiple () const; + virtual int get_frame_offset () const; + virtual int get_freeze_frame_multiple () const; + virtual int get_freeze_frame_offset () const; + virtual bool get_enabled () const; /** Value set in var_cycle command.\n */ - double update_rate ; /**< trick_io(**) */ + double _update_rate ; /**< trick_io(**) */ /** The update rate in integer tics.\n */ - long long cycle_tics ; /**< trick_io(**) */ + long long _cycle_tics ; /**< trick_io(**) */ /** The next call time in integer tics of the job to copy client data (sync mode).\n */ - long long next_tics ; /**< trick_io(**) */ + long long _next_tics ; /**< trick_io(**) */ /** The next call time in integer tics of the job to copy client data (sync mode).\n */ - long long freeze_next_tics ; /**< trick_io(**) */ + long long _freeze_next_tics ; /**< trick_io(**) */ /** The simulation time converted to seconds\n */ - double time ; /**< trick_units(s) */ + double _time ; /**< trick_units(s) */ /** Toggle to set variable server copy as top_of_frame, scheduled, async \n */ - VS_COPY_MODE copy_mode ; /**< trick_io(**) */ + VS_COPY_MODE _copy_mode ; /**< trick_io(**) */ /** Toggle to set variable server writes as when copied or async.\n */ - VS_WRITE_MODE write_mode ; /**< trick_io(**) */ + VS_WRITE_MODE _write_mode ; /**< trick_io(**) */ /** multiples of frame_count to copy data. Only used at top_of_frame\n */ - int frame_multiple ; /**< trick_io(**) */ + int _frame_multiple ; /**< trick_io(**) */ /** multiples of frame_count to copy data. Only used at top_of_frame\n */ - int frame_offset ; /**< trick_io(**) */ + int _frame_offset ; /**< trick_io(**) */ /** multiples of frame_count to copy data. Only used at top_of_frame\n */ - int freeze_frame_multiple ; /**< trick_io(**) */ + int _freeze_frame_multiple ; /**< trick_io(**) */ /** multiples of frame_count to copy data. Only used at top_of_frame\n */ - int freeze_frame_offset ; /**< trick_io(**) */ - - // The way the modes are handled is confusing. TODO: refactor >:( + int _freeze_frame_offset ; /**< trick_io(**) */ /** Toggle to tell variable server to byteswap returned values.\n */ - bool byteswap ; /**< trick_io(**) */ + bool _byteswap ; /**< trick_io(**) */ /** Toggle to tell variable server return data in binary format.\n */ - bool binary_data ; /**< trick_io(**) */ + bool _binary_data ; /**< trick_io(**) */ /** Toggle to tell variable server return data in binary format without the variable names.\n */ - bool binary_data_nonames ; /**< trick_io(**) */ + bool _binary_data_nonames ; /**< trick_io(**) */ /** Value (1,2,or 3) that causes the variable server to output increasing amounts of debug information.\n */ - int debug ; /**< trick_io(**) */ + int _debug ; /**< trick_io(**) */ /** Toggle to enable/disable this variable server thread.\n */ - bool enabled ; /**< trick_io(**) */ + bool _enabled ; /**< trick_io(**) */ /** Toggle to turn on/off variable server logged messages to a playback file.\n */ - bool log ; /**< trick_io(**) */ + bool _log ; /**< trick_io(**) */ /** Toggle to indicate var_pause commanded.\n */ - bool pause_cmd ; /**< trick_io(**) */ + bool _pause_cmd ; /**< trick_io(**) */ /** Save pause state while reloading a checkpoint.\n */ - bool saved_pause_cmd ; /**< trick_io(**) */ + bool _saved_pause_cmd ; /**< trick_io(**) */ - bool send_stdio; + bool _send_stdio; - bool validate_address; + bool _validate_address; + + /** Toggle to indicate var_exit commanded.\n */ + bool _exit_cmd ; /**< trick_io(**) */ - int packets_copied; }; } diff --git a/include/trick/VariableServerSessionThread.hh b/include/trick/VariableServerSessionThread.hh new file mode 100644 index 00000000..8037da31 --- /dev/null +++ b/include/trick/VariableServerSessionThread.hh @@ -0,0 +1,106 @@ +/* + PURPOSE: + (VariableServerSessionThread) +*/ + +#ifndef VariableServerSessionThread_HH +#define VariableServerSessionThread_HH + +#include +#include +#include +#include "trick/SysThread.hh" +#include "trick/VariableServerSession.hh" +#include "trick/variable_server_sync_types.h" +#include "trick/variable_server_message_types.h" + +#include "trick/ClientConnection.hh" +#include "trick/TCPClientListener.hh" + +namespace Trick { + + class VariableServer ; + + /** Flag to indicate the connection has been made */ + enum ConnectionStatus { CONNECTION_PENDING, CONNECTION_SUCCESS, CONNECTION_FAIL }; + + +/** + The purpose of this class is to run a VariableServerSession. + - Manages creation and cleanup of the network connection + - Manages thread startup and shutdown + - Invokes parts of the VariableServerSession that run asynchronously (reading from client, copying and writing to client if in applicable mode) + + @author Alex Lin + @author Jackie Deans (2023) + */ + class VariableServerSessionThread : public Trick::SysThread { + + public: + friend std::ostream& operator<< (std::ostream& s, Trick::VariableServerSessionThread& vst); + + /** + @brief Constructor. + */ + VariableServerSessionThread() ; + VariableServerSessionThread(VariableServerSession * session) ; + + virtual ~VariableServerSessionThread() ; + /** + @brief static routine called from S_define to set the VariableServer pointer for all threads. + @param in_vs - the master variable server object + */ + static void set_vs_ptr(Trick::VariableServer * in_vs) ; + + void set_client_tag(std::string tag); + + /** + @brief Set the connection pointer for this thread + */ + void set_connection(ClientConnection * in_connection); + + /** + @brief Block until thread has accepted connection + */ + ConnectionStatus wait_for_accept() ; + + /** + @brief The main loop of the variable server thread that reads and processes client commands. + @return always 0 + */ + virtual void * thread_body() ; + + VariableServer * get_vs() ; + + void preload_checkpoint() ; + + void restart() ; + + void cleanup(); + + protected: + /** The Master variable server object. */ + static VariableServer * _vs ; + + /** Manages the variable list */ + VariableServerSession * _session; /**< trick_io(**) */ + + /** Connection to the client */ + ClientConnection * _connection; /**< trick_io(**) */ + + /** Value (1,2,or 3) that causes the variable server to output increasing amounts of debug information.\n */ + int _debug ; /**< trick_io(**) */ + + ConnectionStatus _connection_status ; /**< trick_io(**) */ + pthread_mutex_t _connection_status_mutex; /**< trick_io(**) */ + pthread_cond_t _connection_status_cv; /**< trick_io(**) */ + + bool _saved_pause_cmd; + } ; + + std::ostream& operator<< (std::ostream& s, VariableServerSessionThread& vst); + +} + +#endif + diff --git a/include/trick/VariableServerThread.hh b/include/trick/VariableServerThread.hh deleted file mode 100644 index 0b046035..00000000 --- a/include/trick/VariableServerThread.hh +++ /dev/null @@ -1,127 +0,0 @@ -/* - PURPOSE: - (VariableServerThread) -*/ - -#ifndef VARIABLESERVERTHREAD_HH -#define VARIABLESERVERTHREAD_HH - -#include -#include -#include -#include -#include "trick/tc.h" -#include "trick/SysThread.hh" -#include "trick/VariableServerSession.hh" -#include "trick/variable_server_sync_types.h" -#include "trick/variable_server_message_types.h" - -#include "trick/ClientConnection.hh" -#include "trick/ClientListener.hh" - -namespace Trick { - - class VariableServer ; - - /** Flag to indicate the connection has been made\n */ - enum ConnectionStatus { CONNECTION_PENDING, CONNECTION_SUCCESS, CONNECTION_FAIL }; - - -/** - This class provides variable server command processing on a separate thread for each client. - @author Alex Lin - */ - class VariableServerThread : public Trick::SysThread { - - public: - friend std::ostream& operator<< (std::ostream& s, Trick::VariableServerThread& vst); - - /** - @brief Constructor. - @param listen_dev - the TCDevice set up in listen() - */ - VariableServerThread() ; - - virtual ~VariableServerThread() ; - /** - @brief static routine called from S_define to set the VariableServer pointer for all threads. - @param in_vs - the master variable server object - */ - static void set_vs_ptr(Trick::VariableServer * in_vs) ; - - void set_client_tag(std::string tag); - - /** - @brief Open a UDP socket for this thread - */ - int open_udp_socket(const std::string& hostname, int port); - - /** - @brief Open a TCP connection for this thread - */ - int open_tcp_connection(ClientListener * listener); - - /** - @brief Block until thread has accepted connection - */ - ConnectionStatus wait_for_accept() ; - - /** - @brief The main loop of the variable server thread that reads and processes client commands. - @return always 0 - */ - virtual void * thread_body() ; - - VariableServer * get_vs() ; - - void preload_checkpoint() ; - - void restart() ; - - void cleanup(); - - protected: - - /** - @brief Called by send_sie commands to transmit files through the socket. - */ - int transmit_file(std::string file_name); - - /** The Master variable server object. */ - static VariableServer * vs ; - - /** Manages the variable list */ - VariableServerSession * session; /**< trick_io(**) */ - - /** Connection to the client */ - ClientConnection * connection; /**< trick_io(**) */ - - /** Value (1,2,or 3) that causes the variable server to output increasing amounts of debug information.\n */ - int debug ; /**< trick_io(**) */ - - /** Toggle to enable/disable this variable server thread.\n */ - bool enabled ; /**< trick_io(**) */ - - ConnectionStatus connection_status ; /**< trick_io(**) */ - pthread_mutex_t connection_status_mutex; /**< trick_io(**) */ - pthread_cond_t connection_status_cv; /**< trick_io(**) */ - - /** The mutex pauses all processing during checkpoint restart */ - pthread_mutex_t restart_pause ; /**< trick_io(**) */ - - // bool pause_cmd; - bool saved_pause_cmd; - - /** The simulation time converted to seconds\n */ - double time ; /**< trick_units(s) */ - - /** Toggle to tell variable server to send data multicast or point to point.\n */ - bool multicast ; /**< trick_io(**) */ - } ; - - std::ostream& operator<< (std::ostream& s, VariableServerThread& vst); - -} - -#endif - diff --git a/include/trick/var_binary_parser.hh b/include/trick/var_binary_parser.hh index 17a6c4e8..1203c849 100644 --- a/include/trick/var_binary_parser.hh +++ b/include/trick/var_binary_parser.hh @@ -112,7 +112,6 @@ class ParsedBinaryMessage { void combine (const ParsedBinaryMessage& message); int parse (const std::vector& bytes); - int parse (char * raw_bytes); int getMessageType() const; unsigned int getMessageSize() const; diff --git a/share/trick/pymods/trick/tests/test_variable_server.py b/share/trick/pymods/trick/tests/test_variable_server.py index 9ac8dc68..ef72c47d 100644 --- a/share/trick/pymods/trick/tests/test_variable_server.py +++ b/share/trick/pymods/trick/tests/test_variable_server.py @@ -110,7 +110,7 @@ class TestVariableServer(unittest.TestCase): def test_set_period(self): self.variable_server.set_period(10) - # We would like to verify that VariableServerThread::update_rate + # We would like to verify that VariableServerSessionThread::update_rate # was modified, but variable server threads are not registered # with the memory manager, so we can't. @@ -137,38 +137,38 @@ class TestVariableServer(unittest.TestCase): def test_pause(self): self.variable_server.pause(True) self.variable_server.pause(False) - # We would like to verify that VariableServerThread::pause_cmd + # We would like to verify that VariableServerSessionThread::pause_cmd # was modified, but variable server threads are not registered # with the memory manager, so we can't. def test_set_debug(self): self.variable_server.set_debug(3) - # We would like to verify that VariableServerThread::debug + # We would like to verify that VariableServerSessionThread::debug # was modified, but variable server threads are not registered # with the memory manager, so we can't. def test_set_tag(self): self.variable_server.set_tag('test') # We would like to verify that - # VariableServerThread::connection.client_tag was modified, but + # VariableServerSessionThread::connection.client_tag was modified, but # variable server threads are not registered with the memory # manager, so we can't. def test_set_copy_mode(self): self.variable_server.set_copy_mode() - # We would like to verify that VariableServerThread::copy_mode + # We would like to verify that VariableServerSessionThread::copy_mode # was modified, but variable server threads are not registered # with the memory manager, so we can't. def test_send_on_copy(self): self.variable_server.send_on_copy() - # We would like to verify that VariableServerThread::write_mode + # We would like to verify that VariableServerSessionThread::write_mode # was modified, but variable server threads are not registered # with the memory manager, so we can't. def test_validate_addresses(self): self.variable_server.validate_addresses() - # We would like to verify that VariableServerThread::validate_addresses + # We would like to verify that VariableServerSessionThread::validate_addresses # was modified, but variable server threads are not registered # with the memory manager, so we can't. diff --git a/share/trick/sim_objects/default_trick_sys.sm b/share/trick/sim_objects/default_trick_sys.sm index 9b7a871e..4c57d5cf 100644 --- a/share/trick/sim_objects/default_trick_sys.sm +++ b/share/trick/sim_objects/default_trick_sys.sm @@ -456,22 +456,22 @@ class VariableServerSimObject : public Trick::SimObject { {TRK} ("preload_checkpoint") vs.suspendPreCheckpointReload(); {TRK} ("restart") vs.restart(); {TRK} ("restart") vs.resumePostCheckpointReload(); - {TRK} ("top_of_frame") vs.copy_data_top() ; - {TRK} ("automatic_last") vs.copy_data_scheduled() ; + {TRK} ("top_of_frame") vs.copy_and_write_top() ; + {TRK} ("automatic_last") vs.copy_and_write_scheduled() ; {TRK} ("freeze_init") vs.freeze_init() ; - {TRK} ("freeze_automatic") vs.copy_data_freeze_scheduled() ; - {TRK} ("freeze") vs.copy_data_freeze() ; + {TRK} ("freeze_automatic") vs.copy_and_write_freeze_scheduled() ; + {TRK} ("freeze") vs.copy_and_write_freeze() ; {TRK} ("shutdown") vs.shutdown() ; - // Set the static VariableServer pointer in the VariableServerThread class. - Trick::VariableServerThread::set_vs_ptr(&vs) ; + // Set the static VariableServer pointer in the VariableServerSessionThread class. + Trick::VariableServerSessionThread::set_vs_ptr(&vs) ; // get the process_event job and set it in the event processor. - vs.set_copy_data_job(get_job("vs.copy_data_scheduled")) ; - vs.set_copy_data_freeze_job(get_job("vs.copy_data_freeze_scheduled")) ; + vs.set_copy_data_job(get_job("vs.copy_and_write_scheduled")) ; + vs.set_copy_and_write_freeze_job(get_job("vs.copy_and_write_freeze_scheduled")) ; } private: diff --git a/share/trick/trickops/WorkflowCommon.py b/share/trick/trickops/WorkflowCommon.py index 6b164cc4..8b8e0e86 100644 --- a/share/trick/trickops/WorkflowCommon.py +++ b/share/trick/trickops/WorkflowCommon.py @@ -427,7 +427,7 @@ class Job(object): by sending them the SIGKILL signal. """ try: - os.killpg(os.getpgid(self._process.pid), signal.SIGKILL) + os.killpg(os.getpgid(self._process.pid), signal.SIGABRT) self._process.wait() except: pass diff --git a/test/.gitignore b/test/.gitignore index f3d79c4a..2cb1c8bc 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -27,3 +27,4 @@ build S_sie.json *.ckpnt MonteCarlo_Meta_data_output +*.xml \ No newline at end of file diff --git a/test/SIM_test_varserv/RUN_test/err1_test.py b/test/SIM_test_varserv/RUN_test/err1_test.py new file mode 100644 index 00000000..ca34b4e7 --- /dev/null +++ b/test/SIM_test_varserv/RUN_test/err1_test.py @@ -0,0 +1,24 @@ +import trick + +from trick.unit_test import * + +def main(): + + trick.var_server_set_port(40000) + trick.var_ascii() + trick.real_time_enable() + trick.exec_set_software_frame(0.01) + + trick.exec_set_terminate_time(1000.0) + trick.var_server_set_source_address('localhost') + + varServerPort = trick.var_server_get_port() + test_output = ( os.getenv("TRICK_HOME") + "/trick_test/SIM_test_varserv.xml" ) + command = 'os.system("./models/test_client/test_client_err1 ' + str(varServerPort) + ' --gtest_output=xml:' + test_output + ' &")' + + # Start the test client after everything has been initialized (hopefully) + trick.add_read(1.0, command) + +if __name__ == "__main__": + main() + diff --git a/test/SIM_test_varserv/RUN_test/err2_test.py b/test/SIM_test_varserv/RUN_test/err2_test.py new file mode 100644 index 00000000..e5ce5927 --- /dev/null +++ b/test/SIM_test_varserv/RUN_test/err2_test.py @@ -0,0 +1,24 @@ +import trick + +from trick.unit_test import * + +def main(): + + trick.var_server_set_port(40000) + trick.var_ascii() + trick.real_time_enable() + trick.exec_set_software_frame(0.01) + + trick.exec_set_terminate_time(1000.0) + trick.var_server_set_source_address('localhost') + + varServerPort = trick.var_server_get_port() + test_output = ( os.getenv("TRICK_HOME") + "/trick_test/SIM_test_varserv.xml" ) + command = 'os.system("./models/test_client/test_client_err2 ' + str(varServerPort) + ' --gtest_output=xml:' + test_output + ' &")' + + # Start the test client after everything has been initialized (hopefully) + trick.add_read(1.0, command) + +if __name__ == "__main__": + main() + diff --git a/test/SIM_test_varserv/RUN_test/manual_test.py b/test/SIM_test_varserv/RUN_test/manual_test.py index 982de3ce..ac7013fa 100644 --- a/test/SIM_test_varserv/RUN_test/manual_test.py +++ b/test/SIM_test_varserv/RUN_test/manual_test.py @@ -5,7 +5,7 @@ from trick.unit_test import * def main(): - trick.var_server_set_port(4000) + trick.var_server_set_port(40000) trick.var_ascii() trick.real_time_enable() trick.exec_set_software_frame(0.01) diff --git a/test/SIM_test_varserv/RUN_test/unit_test.py b/test/SIM_test_varserv/RUN_test/unit_test.py index afd494eb..335dba25 100644 --- a/test/SIM_test_varserv/RUN_test/unit_test.py +++ b/test/SIM_test_varserv/RUN_test/unit_test.py @@ -1,5 +1,4 @@ import trick -import socket from trick.unit_test import * @@ -11,9 +10,11 @@ def main(): trick.exec_set_software_frame(0.01) # trick.set_var_server_info_msg_on() - trick.var_server_create_tcp_socket('localhost', 49000) - trick.var_server_create_udp_socket('', 48000) - trick.var_server_create_multicast_socket('224.10.10.10','', 47000) + hostname = trick.var_server_get_hostname() + + trick.var_server_create_tcp_socket(hostname, 49000) + trick.var_server_create_udp_socket(hostname, 48000) + trick.var_server_create_multicast_socket('224.10.10.10',hostname, 47000) trick.exec_set_terminate_time(100.0) diff --git a/test/SIM_test_varserv/S_overrides.mk b/test/SIM_test_varserv/S_overrides.mk index 1157f7b9..96897111 100644 --- a/test/SIM_test_varserv/S_overrides.mk +++ b/test/SIM_test_varserv/S_overrides.mk @@ -2,7 +2,7 @@ TRICK_CFLAGS += -I./models TRICK_CXXFLAGS += -I./models -all: test_client +all: test_client test_client_err1 test_client_err2 clean: clean_test_client TEST_CLIENT_LIBS += -L${GTEST_HOME}/lib64 -L${GTEST_HOME}/lib -lgtest -lgtest_main -lpthread -L${TRICK_LIB_DIR} -ltrick_var_binary_parser @@ -10,6 +10,12 @@ TEST_CLIENT_LIBS += -L${GTEST_HOME}/lib64 -L${GTEST_HOME}/lib -lgtest -lgtest_ma test_client: models/test_client/test_client.cpp cd models/test_client; $(TRICK_CXX) test_client.cpp -o test_client $(TRICK_CXXFLAGS) $(TRICK_SYSTEM_CXXFLAGS) $(TRICK_TEST_FLAGS) -Wno-write-strings -Wno-sign-compare -I$(TRICK_HOME)/include $(TEST_CLIENT_LIBS) +test_client_err1: models/test_client/test_client_err1.cpp + cd models/test_client; $(TRICK_CXX) test_client_err1.cpp -o test_client_err1 $(TRICK_CXXFLAGS) $(TRICK_SYSTEM_CXXFLAGS) $(TRICK_TEST_FLAGS) -Wno-write-strings -Wno-sign-compare -I$(TRICK_HOME)/include $(TEST_CLIENT_LIBS) + +test_client_err2: models/test_client/test_client_err2.cpp + cd models/test_client; $(TRICK_CXX) test_client_err2.cpp -o test_client_err2 $(TRICK_CXXFLAGS) $(TRICK_SYSTEM_CXXFLAGS) $(TRICK_TEST_FLAGS) -Wno-write-strings -Wno-sign-compare -I$(TRICK_HOME)/include $(TEST_CLIENT_LIBS) + clean_test_client: - rm -f models/test_client/test_client + rm -f models/test_client/test_client models/test_client/test_client_err1 models/test_client/test_client_err2 diff --git a/test/SIM_test_varserv/file_to_send.txt b/test/SIM_test_varserv/file_to_send.txt new file mode 100644 index 00000000..b34b754c --- /dev/null +++ b/test/SIM_test_varserv/file_to_send.txt @@ -0,0 +1 @@ +hi hello world \ No newline at end of file diff --git a/test/SIM_test_varserv/models/test_client/.gitignore b/test/SIM_test_varserv/models/test_client/.gitignore index 837a5b2a..25864a77 100644 --- a/test/SIM_test_varserv/models/test_client/.gitignore +++ b/test/SIM_test_varserv/models/test_client/.gitignore @@ -1 +1,3 @@ -test_client \ No newline at end of file +test_client +test_client_err1 +test_client_err2 \ No newline at end of file diff --git a/test/SIM_test_varserv/models/test_client/test_client.cpp b/test/SIM_test_varserv/models/test_client/test_client.cpp index 6d172d39..3d30d9ba 100644 --- a/test/SIM_test_varserv/models/test_client/test_client.cpp +++ b/test/SIM_test_varserv/models/test_client/test_client.cpp @@ -1,222 +1,7 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - +#include "test_client.hh" #include "trick/var_binary_parser.hh" +#include -#define DOUBLE_TOL 1e-5 -#define SOCKET_BUF_SIZE 204800 - -// Pretend that gtest was kind enough to provide an EXPECT_FEQ operator with a tolerance -#define EXPECT_FEQ(a,b) EXPECT_LE(fabs(a - b), DOUBLE_TOL) - -class Socket { - - public: - - int max_retries = 10; - - Socket() : _initialized(false) {} - int init(std::string hostname, int port, int mode = SOCK_STREAM) { - _multicast_socket = false; - - _hostname = hostname; - _port = port; - int tries = 0; - - while ((_socket_fd = socket(AF_INET, mode, 0)) < 0 && tries < max_retries) tries++; - - if (_socket_fd < 0) { - std::cout << "Socket connection failed" << std::endl; - return -1; - } - - struct sockaddr_in serv_addr; - serv_addr.sin_family = AF_INET; - serv_addr.sin_port = htons(port); // convert to weird network byte format - - if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) { - std::cout << "Invalid address/ Address not supported" << std::endl; - return -1; - } - - tries = 0; - int connection_status; - - while ((connection_status = connect(_socket_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) < 0 && tries < max_retries) tries++; - - if (connection_status < 0) { - std::cout << "Connection failed" << std::endl; - return -1; - } - - _initialized = true; - - return 0; - } - - #ifndef __APPLE__ - int init_multicast (std::string hostname, int port) { - _multicast_socket = true; - _hostname = hostname; - _port = port; - int tries = 0; - - while ((_socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 && tries < max_retries) tries++; - - if (_socket_fd < 0) { - std::cout << "init_multicast: Socket open failed" << std::endl; - return -1; - } - - int value = 1; - if (setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &value, (socklen_t) sizeof(value)) < 0) { - std::cout << "init_multicast: Socket option failed" << std::endl; - return -1; - } - - struct ip_mreq mreq; - // Use setsockopt() to request that the kernel join a multicast group - mreq.imr_multiaddr.s_addr = inet_addr(_hostname.c_str()); - mreq.imr_interface.s_addr = htonl(INADDR_ANY); - if (setsockopt(_socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, (socklen_t) sizeof(mreq)) < 0) { - std::cout << "init_multicast: setsockopt: ip_add_membership failed" << std::endl; - return -1; - } - - - struct sockaddr_in sockin ; - - // Set up destination address - sockin.sin_family = AF_INET; - sockin.sin_addr.s_addr = htonl(INADDR_ANY); - sockin.sin_port = htons(_port); - - if ( bind(_socket_fd, (struct sockaddr *) &sockin, (socklen_t) sizeof(sockin)) < 0 ) { - std::cout << "init_multicast: bind failed" << std::endl; - return -1; - } - - _initialized = true; - return 0; - } - - #endif - - int send (std::string message) { - // weird syntax I've never used before - since the send syscall that i'm trying to use is overloaded in this class, - // I have to append :: to the front of it so that the compiler knows to look in the global namespace - int success = ::send(_socket_fd, message.c_str(), message.size(), 0); - if (success < message.size()) { - std::cout << "init_multicast: Failed to send message" << std::endl; - } - return success; - } - - int operator<< (std::string message) { - return send(message); - } - - std::string receive () { - char buffer[SOCKET_BUF_SIZE]; - int numBytes = recv(_socket_fd, buffer, SOCKET_BUF_SIZE, 0); - if (numBytes < 0) { - } else if (numBytes < SOCKET_BUF_SIZE) { - buffer[numBytes] = '\0'; - } - - return std::string(buffer); - } - - void operator>> (std::string& ret) { - ret = receive(); - } - - std::vector receive_bytes() { - unsigned char buffer[SOCKET_BUF_SIZE]; - int numBytes = recv(_socket_fd, buffer, SOCKET_BUF_SIZE, 0); - if (numBytes < 0) { - std::cout << "init_multicast: Failed to read from socket" << std::endl; - } - - std::vector bytes; - for (int i = 0; i < numBytes; i++) { - bytes.push_back(buffer[i]); - } - - return bytes; - } - - bool check_for_message_availible(long timeout_sec = 2) { - fd_set read_fd_set; - struct timeval timeout = { .tv_sec = timeout_sec, .tv_usec = 0 }; - FD_ZERO(&read_fd_set); - FD_SET(_socket_fd, &read_fd_set); - - // I have one question for the designers of the interface for this syscall: why - select(_socket_fd+1, &read_fd_set, NULL, NULL, &timeout); - - return FD_ISSET(_socket_fd, &read_fd_set); - } - - void clear_buffered_data() { - // Poll the socket - if (check_for_message_availible(0)) { - // Receive into the void if there is a message - receive(); - } - // otherwise no worries - } - - void close() { - ::close(_socket_fd); - } - - private: - int _port; - std::string _hostname; - int _socket_fd; - bool _initialized; - bool _multicast_socket; - -}; - -class VariableServerTest : public ::testing::Test { - protected: - VariableServerTest() { - socket_status = socket.init("localhost", 40000); - if (socket_status == 0) { - std::stringstream request; - request << "trick.var_set_client_tag(\"VSTest"; - request << numSession++; - request << "\") \n"; - - socket << request.str(); - } - } - ~VariableServerTest() { - socket << "trick.var_exit()\n"; - socket.close(); - } - - Socket socket; - int socket_status; - - static int numSession; -}; class VariableServerUDPTest : public ::testing::Test { protected: @@ -271,7 +56,36 @@ int VariableServerTest::numSession = 0; int VariableServerUDPTest::numSession = 0; int VariableServerTestAltListener::numSession = 0; +#ifndef __APPLE__ +class VariableServerTestMulticast : public ::testing::Test { + protected: + VariableServerTestMulticast() { + socket_status = socket.init("", 47000, SOCK_DGRAM); + socket_status = multicast_listener.init_multicast("224.10.10.10", 47000); + if (socket_status == 0) { + std::stringstream request; + request << "trick.var_set_client_tag(\"multicast_VSTest"; + request << numSession++; + request << "\") \n"; + + socket << request.str(); + } + } + ~VariableServerTestMulticast() { + socket.close(); + multicast_listener.close(); + } + + Socket socket; + Socket multicast_listener; + + int socket_status; + + static int numSession; +}; +int VariableServerTestMulticast::numSession = 0; +#endif /**********************************************************/ /* Helpful constants and functions */ @@ -345,6 +159,97 @@ void load_checkpoint (Socket& socket, const std::string& checkpoint_name) { socket << "trick.var_unpause()\n"; } +/*****************************************/ +/* Multicast Test */ +/*****************************************/ + +#ifndef __APPLE__ + +TEST_F (VariableServerTestMulticast, Strings) { + if (socket_status != 0) { + FAIL(); + } + + std::string reply; + socket << "trick.var_send_once(\"vsx.vst.o\")\n"; + std::string expected("5\tYou will rejoice to hear that no disaster has accompanied the commencement of an enterprise which you have regarded with such evil forebodings. I arrived here yesterday, and my first task is to assure my dear sister of my welfare and increasing confidence in the success of my undertaking."); + + multicast_listener >> reply; + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + + expected = std::string("5\tI am already far north of London, and as I walk in the streets of Petersburgh, I feel a cold northern breeze play upon my cheeks, which braces my nerves and fills me with delight. Do you understand this feeling?"); + socket << "trick.var_send_once(\"vsx.vst.p\")\n"; + + multicast_listener >> reply; + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); +} + + +TEST_F (VariableServerTestMulticast, AddRemove) { + if (socket_status != 0) { + FAIL(); + } + + std::string reply; + std::string expected; + + int max_tries = 3; + int tries = 0; + + socket << "trick.var_add(\"vsx.vst.c\")\n"; + multicast_listener >> reply; + expected = std::string("0 -1234"); + + tries = 0; + while (strcmp_IgnoringWhiteSpace(reply, expected) != 0 && tries++ < max_tries) { + multicast_listener >> reply; + } + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0) << "Expected: " << expected << "\tAcutal: " << reply; + + multicast_listener >> reply; + tries = 0; + while (strcmp_IgnoringWhiteSpace(reply, expected) != 0 && tries++ < max_tries) { + multicast_listener >> reply; + } + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0) << "Expected: " << expected << "\tAcutal: " << reply; + + socket << "trick.var_add(\"vsx.vst.m\")\n"; + multicast_listener >> reply; + expected = std::string("0 -1234 1"); + tries = 0; + while (strcmp_IgnoringWhiteSpace(reply, expected) != 0 && tries++ < max_tries) { + multicast_listener >> reply; + } + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0) << "Expected: " << expected << "\tAcutal: " << reply; + + socket << "trick.var_remove(\"vsx.vst.m\")\n"; + multicast_listener >> reply; + expected = std::string("0 -1234"); + tries = 0; + while (strcmp_IgnoringWhiteSpace(reply, expected) != 0 && tries++ < max_tries) { + multicast_listener >> reply; + } + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0) << "Expected: " << expected << "\tAcutal: " << reply; + + socket << "trick.var_add(\"vsx.vst.n\")\n"; + multicast_listener >> reply; + expected = std::string("0 -1234 0,1,2,3,4"); + tries = 0; + while (strcmp_IgnoringWhiteSpace(reply, expected) != 0 && tries++ < max_tries) { + multicast_listener >> reply; + } + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0) << "Expected: " << expected << "\tAcutal: " << reply; + + socket << "trick.var_exit()\n"; +} + +#endif + /************************************/ /* UDP Tests */ /************************************/ @@ -501,7 +406,7 @@ TEST_F (VariableServerTestAltListener, RestartAndSet) { load_checkpoint(socket, "RUN_test/reload_file.ckpnt"); socket >> reply; - expected = std::string("0\t-1234\t-123456\n"); + expected = std::string("0\t-1234\t-123456 {m}\n"); EXPECT_EQ(reply, expected); } @@ -658,6 +563,18 @@ TEST_F (VariableServerTest, Units) { EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); } +TEST_F (VariableServerTest, dump_info) { + if (socket_status != 0) { + FAIL(); + } + socket << "trick.var_add(\"vsx.vst.c\")\n"; + + std::string command = "trick.var_server_list_connections()\n"; + socket << command; + sleep(1); +} + + TEST_F (VariableServerTest, SendOnce) { if (socket_status != 0) { FAIL(); @@ -764,6 +681,153 @@ TEST_F (VariableServerTest, ListSize) { } +TEST_F (VariableServerTest, bitfields) { + if (socket_status != 0) { + FAIL(); + } + + socket << "trick.var_send_once(\"vsx.vst.my_bitfield.var1,vsx.vst.my_bitfield.var2,vsx.vst.my_bitfield.var3,vsx.vst.my_bitfield.var4\", 4)\n"; + std::string reply_str; + socket >> reply_str; + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply_str, "5 3 -128 0 2112"), 0); + + socket << "trick.var_binary()\ntrick.var_send_once(\"vsx.vst.my_bitfield.var1,vsx.vst.my_bitfield.var2,vsx.vst.my_bitfield.var3,vsx.vst.my_bitfield.var4\", 4)\n"; + + std::vector reply = socket.receive_bytes(); + + ParsedBinaryMessage message; + try { + message.parse(reply); + } catch (const MalformedMessageException& ex) { + FAIL() << "Parser threw an exception: " << ex.what(); + } + + for (int i = 0; i < message.getNumVars(); i++) { + EXPECT_TRUE(message.variables[i].getType() == 12 || message.variables[i].getType() == 13); + } +} + +TEST_F (VariableServerTest, transmit_file) { + if (socket_status != 0) { + FAIL(); + } + + socket << "trick.send_file(\"file_to_send.txt\")\n"; + + std::stringstream file_contents; + + while (socket.check_for_message_availible()) { + socket >> file_contents; + } + + std::ifstream actual_file("file_to_send.txt"); + std::string expected_contents; + getline(actual_file, expected_contents); + + std::string expected_header = "2\t" + std::to_string(expected_contents.size()); + + std::string test_line; + + std::getline(file_contents, test_line); + EXPECT_EQ(test_line, expected_header); + + std::getline(file_contents, test_line); + EXPECT_EQ(test_line, expected_contents); +} + + +TEST_F (VariableServerTest, SendSieResource) { + if (socket_status != 0) { + FAIL(); + } + + socket << "trick.send_sie_resource()\n"; + + std::stringstream received_file; + + while (socket.check_for_message_availible()) { + socket >> received_file; + } + + std::string first_line; + std::getline(received_file, first_line); + + // We're not gonna bother comparing the contents + // Just check to see that the message type is correct + ASSERT_EQ(first_line.at(0), '2'); +} + +TEST_F (VariableServerTest, SendSieClass) { + if (socket_status != 0) { + FAIL(); + } + + socket << "trick.send_sie_class()\n"; + + std::stringstream received_file; + std::string file_temp; + + while (socket.check_for_message_availible()) { + socket >> file_temp; + received_file << file_temp; + } + + std::string first_line; + std::getline(received_file, first_line); + + // We're not gonna bother comparing the contents + // Just check to see that the message type is correct + ASSERT_EQ(first_line.at(0), '2'); +} + +TEST_F (VariableServerTest, SendSieEnum) { + if (socket_status != 0) { + FAIL(); + } + + socket << "trick.send_sie_enum()\n"; + + std::stringstream received_file; + std::string file_temp; + + while (socket.check_for_message_availible()) { + socket >> file_temp; + received_file << file_temp; + } + + std::string first_line; + std::getline(received_file, first_line); + + // We're not gonna bother comparing the contents + // Just check to see that the message type is correct + ASSERT_EQ(first_line.at(0), '2'); +} + + +TEST_F (VariableServerTest, SendSieClassTopLevelObjects) { + if (socket_status != 0) { + FAIL(); + } + + socket << "trick.send_sie_top_level_objects()\n"; + + std::stringstream received_file; + std::string file_temp; + + while (socket.check_for_message_availible()) { + socket >> file_temp; + received_file << file_temp; + } + + std::string first_line; + std::getline(received_file, first_line); + + // We're not gonna bother comparing the contents + // Just check to see that the message type is correct + ASSERT_EQ(first_line.at(0), '2'); +} + TEST_F (VariableServerTest, RestartAndSet) { if (socket_status != 0) { FAIL(); @@ -793,7 +857,7 @@ TEST_F (VariableServerTest, RestartAndSet) { load_checkpoint(socket, "RUN_test/reload_file.ckpnt"); socket >> reply; - expected = std::string("0\t-1234\t-123456\n"); + expected = std::string("0\t-1234\t-123456 {m}\n"); EXPECT_EQ(reply, expected); } @@ -1017,7 +1081,28 @@ TEST_F (VariableServerTest, Freeze) { // Sim mode should be MODE_FREEZE == 1 ASSERT_EQ(mode, MODE_FREEZE); - // Not sure what else to test in copy mode VS_COPY_ASYNC + // Set to back to run mode + socket << "trick.exec_run()\n"; + + // The mode takes a little bit of time to change + wait_for_mode_change(MODE_RUN); + + // Sim mode should be MODE_RUN + ASSERT_EQ(mode, MODE_RUN); + + // Test VS_COPY_SCHEDULED - should see 1 message per frame + socket << "trick.var_set_copy_mode(1)\n"; + + + // Change back to freeze mode + // Set to Freeze mode + socket << "trick.exec_freeze()\n"; + + // The mode takes a little bit of time to change + wait_for_mode_change(MODE_FREEZE); + + // Sim mode should be MODE_FREEZE == 1 + ASSERT_EQ(mode, MODE_FREEZE); // Test VS_COPY_SCHEDULED - should see 1 message per frame socket << "trick.var_set_copy_mode(1)\n"; @@ -1104,24 +1189,26 @@ TEST_F (VariableServerTest, CopyAndWriteModes) { socket.clear_buffered_data(); // Copy mode 1 (VS_COPY_SCHEDULED) write mode 0 (VS_WRITE_ASYNC) - command = "trick.var_clear()\n" + test_vars_command + "trick.var_set_copy_mode(1)\ntrick.var_add(\"vsx.vst.c\")\ntrick.var_add(\"vsx.vst.d\")\ntrick.var_unpause()\n"; + command = "trick.var_clear()\n" + test_vars_command + "trick.var_sync(1)\ntrick.var_add(\"vsx.vst.c\")\ntrick.var_add(\"vsx.vst.d\")\ntrick.var_unpause()\n"; socket << command; // With copy mode VS_COPY_SCHEDULED and write mode VS_WRITE_ASYNC, the first reply will be all 0 since the main time to copy has not occurred yet. // Is this what we want? Maybe we should have more strict communication on whether the data has been staged so the first message isn't incorrect - // spin(); + + spin(socket); expected = "-1234 1234"; parse_message(socket.receive()); EXPECT_EQ(strcmp_IgnoringWhiteSpace(vars, expected), 0) << "Received: " << vars << " Expected: " << expected; + // Test that we see a difference of exactly expected_cycle (with a tolerance for floating point issues) int prev_frame = 0; double prev_time = 0; for (int i = 0; i < num_tests; i++) { prev_time = sim_time; parse_message(socket.receive()); - EXPECT_FEQ(sim_time - prev_time, expected_cycle); + EXPECT_LT(fmod(sim_time - prev_time, expected_cycle), DOUBLE_TOL); EXPECT_EQ(strcmp_IgnoringWhiteSpace(vars, expected), 0) << "Received: " << vars << " Expected: " << expected; } @@ -1146,7 +1233,7 @@ TEST_F (VariableServerTest, CopyAndWriteModes) { for (int i = 0; i < num_tests; i++) { prev_time = sim_time; parse_message(socket.receive()); - EXPECT_FEQ(sim_time - prev_time, expected_cycle); + EXPECT_LT(fmod(sim_time - prev_time, expected_cycle), DOUBLE_TOL); EXPECT_EQ(strcmp_IgnoringWhiteSpace(vars, expected), 0) << "Received: " << vars << " Expected: " << expected; } @@ -1210,6 +1297,27 @@ TEST_F (VariableServerTest, CopyAndWriteModes) { socket.clear_buffered_data(); } +TEST_F (VariableServerTest, var_set) { + if (socket_status != 0) { + FAIL(); + } + + std::string reply; + std::string expected; + + socket << "trick.var_add(\"vsx.vst.c\")\ntrick.var_add(\"vsx.vst.blocked_from_output\")\ntrick.var_add(\"vsx.vst.blocked_from_input\")\n"; + + socket >> reply; + expected = std::string("0\t-1234\t0\t500\n"); + + EXPECT_EQ(reply, expected); + + socket << "trick.var_set(\"vsx.vst.blocked_from_input\", 0)\n"; + socket << "trick.var_set(\"vsx.vst.blocked_from_output\", 0)\n"; + + +} + bool getCompleteBinaryMessage(ParsedBinaryMessage& message, Socket& socket, bool print = false) { static const int max_retries = 5; diff --git a/test/SIM_test_varserv/models/test_client/test_client.hh b/test/SIM_test_varserv/models/test_client/test_client.hh new file mode 100644 index 00000000..50e66b5b --- /dev/null +++ b/test/SIM_test_varserv/models/test_client/test_client.hh @@ -0,0 +1,238 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#define DOUBLE_TOL 1e-5 +#define SOCKET_BUF_SIZE 204800 + +// Pretend that gtest was kind enough to provide an EXPECT_FEQ operator with a tolerance +#define EXPECT_FEQ(a,b) EXPECT_LE(fabs(a - b), DOUBLE_TOL) + +class Socket { + + public: + + int max_retries = 10; + + Socket() : _initialized(false) {} + int init(std::string hostname, int port, int mode = SOCK_STREAM) { + _multicast_socket = false; + + _hostname = hostname; + _port = port; + int tries = 0; + + _socket_fd = socket(AF_INET, mode, 0); + if (_socket_fd < 0) { + std::cout << "Socket connection failed" << std::endl; + return -1; + } + + int value = 1; + if (setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &value, (socklen_t) sizeof(value)) < 0) { + std::cout << "init_multicast: Socket option failed" << std::endl; + return -1; + } + + struct sockaddr_in serv_addr; + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(port); // convert to weird network byte format + + if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) { + std::cout << "Invalid address/ Address not supported" << std::endl; + return -1; + } + + tries = 0; + int connection_status; + + connection_status = connect(_socket_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); + if (connection_status < 0) { + std::cout << "Connection failed" << std::endl; + return -1; + } + + _initialized = true; + + return 0; + } + #ifndef __APPLE__ + int init_multicast (std::string hostname, int port) { + _multicast_socket = true; + _hostname = hostname; + _port = port; + int tries = 0; + + _socket_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (_socket_fd < 0) { + std::cout << "init_multicast: Socket open failed" << std::endl; + return -1; + } + + int value = 1; + if (setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &value, (socklen_t) sizeof(value)) < 0) { + std::cout << "init_multicast: Socket option failed" << std::endl; + return -1; + } + + if (setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEPORT, (char *) &value, sizeof(value)) < 0) { + perror("setsockopt: reuseport"); + } + + struct ip_mreq mreq; + // Use setsockopt() to request that the kernel join a multicast group + mreq.imr_multiaddr.s_addr = inet_addr(_hostname.c_str()); + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + if (setsockopt(_socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, (socklen_t) sizeof(mreq)) < 0) { + std::cout << "init_multicast: setsockopt: ip_add_membership failed" << std::endl; + return -1; + } + + + struct sockaddr_in sockin ; + + // Set up local interface + // We must bind to the multicast address + sockin.sin_family = AF_INET; + sockin.sin_addr.s_addr = inet_addr(_hostname.c_str()); + sockin.sin_port = htons(_port); + + if ( bind(_socket_fd, (struct sockaddr *) &sockin, (socklen_t) sizeof(sockin)) < 0 ) { + std::cout << "init_multicast: bind failed" << std::endl; + return -1; + } + + char loopch = 1; + + if(setsockopt(_socket_fd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopch, sizeof(loopch)) < 0) + { + perror("Setting IP_MULTICAST_LOOP error"); + return -1; + } + + _initialized = true; + return 0; + } + + #endif + + int send (std::string message) { + // weird syntax I've never used before - since the send syscall that i'm trying to use is overloaded in this class, + // I have to append :: to the front of it so that the compiler knows to look in the global namespace + int success = ::send(_socket_fd, message.c_str(), message.size(), 0); + if (success < message.size()) { + std::cout << "init_multicast: Failed to send message" << std::endl; + } + return success; + } + + int operator<< (std::string message) { + return send(message); + } + + std::string receive () { + char buffer[SOCKET_BUF_SIZE]; + int numBytes = recv(_socket_fd, buffer, SOCKET_BUF_SIZE, 0); + if (numBytes < 0) { + } else if (numBytes < SOCKET_BUF_SIZE) { + buffer[numBytes] = '\0'; + } + + return std::string(buffer); + } + + void operator>> (std::string& ret) { + ret = receive(); + } + + void operator>> (std::ostream& stream) { + stream << receive(); + } + + std::vector receive_bytes() { + unsigned char buffer[SOCKET_BUF_SIZE]; + int numBytes = recv(_socket_fd, buffer, SOCKET_BUF_SIZE, 0); + if (numBytes < 0) { + std::cout << "init_multicast: Failed to read from socket" << std::endl; + } + + std::vector bytes; + for (int i = 0; i < numBytes; i++) { + bytes.push_back(buffer[i]); + } + + return bytes; + } + + bool check_for_message_availible(long timeout_sec = 2) { + fd_set read_fd_set; + struct timeval timeout = { .tv_sec = timeout_sec, .tv_usec = 0 }; + FD_ZERO(&read_fd_set); + FD_SET(_socket_fd, &read_fd_set); + + // I have one question for the designers of the interface for this syscall: why + select(_socket_fd+1, &read_fd_set, NULL, NULL, &timeout); + + return FD_ISSET(_socket_fd, &read_fd_set); + } + + void clear_buffered_data() { + // Poll the socket + if (check_for_message_availible(0)) { + // Receive into the void if there is a message + receive(); + } + // otherwise no worries + } + + void close() { + ::close(_socket_fd); + } + + private: + int _port; + std::string _hostname; + int _socket_fd; + bool _initialized; + bool _multicast_socket; + +}; + +class VariableServerTest : public ::testing::Test { + protected: + VariableServerTest() { + socket_status = socket.init("localhost", 40000); + if (socket_status == 0) { + std::stringstream request; + request << "trick.var_set_client_tag(\"VSTest"; + request << numSession++; + request << "\") \n"; + + socket << request.str(); + } + } + ~VariableServerTest() { + socket << "trick.var_exit()\n"; + socket.close(); + } + + Socket socket; + int socket_status; + + static int numSession; +}; \ No newline at end of file diff --git a/test/SIM_test_varserv/models/test_client/test_client_err1.cpp b/test/SIM_test_varserv/models/test_client/test_client_err1.cpp new file mode 100644 index 00000000..8d4e5d18 --- /dev/null +++ b/test/SIM_test_varserv/models/test_client/test_client_err1.cpp @@ -0,0 +1,13 @@ +#include "test_client.hh" + +int VariableServerTest::numSession = 0; + + +TEST_F (VariableServerTest, SendExecutiveException) { + if (socket_status != 0) { + FAIL(); + } + + std::string command = "trick.exec_terminate_with_return(1, \"" + std::string(__FILE__) + "\", " + std::to_string(__LINE__) + ", \"Error termination for testing\")\n"; + socket << command; +} diff --git a/test/SIM_test_varserv/models/test_client/test_client_err2.cpp b/test/SIM_test_varserv/models/test_client/test_client_err2.cpp new file mode 100644 index 00000000..f32af43d --- /dev/null +++ b/test/SIM_test_varserv/models/test_client/test_client_err2.cpp @@ -0,0 +1,13 @@ +#include "test_client.hh" + +int VariableServerTest::numSession = 0; + + +TEST_F (VariableServerTest, SendStdException) { + if (socket_status != 0) { + FAIL(); + } + + std::string command = "vsx.vst.throw_exception()\n"; + socket << command; +} diff --git a/test/SIM_test_varserv/models/varserv/include/VS.hh b/test/SIM_test_varserv/models/varserv/include/VS.hh index 5aa7c205..270f3a6c 100644 --- a/test/SIM_test_varserv/models/varserv/include/VS.hh +++ b/test/SIM_test_varserv/models/varserv/include/VS.hh @@ -13,13 +13,21 @@ PROGRAMMERS: ( (Lindsay Landry) (L3) (9-12-2013) ) ( (Jackie Dea #ifndef VS_HH #define VS_HH +typedef struct { + unsigned char var1 :3; + short var2 :8; + int var3 :9; + unsigned int var4 :12; +} bitfield; + + class VSTest { public: char a; unsigned char b; short c; unsigned short d; - int e; /* m xy-position */ + int e; /* m xy-position */ unsigned int f; long g; unsigned long h; @@ -31,10 +39,15 @@ class VSTest { int n[5]; std::string o; char * p; - wchar_t * q; /**< trick_chkpnt_io(**) */ + wchar_t * q; /**< trick_chkpnt_io(**) */ + + bitfield my_bitfield; int large_arr[4000]; + int blocked_from_input; /** trick_io(*o) */ + int blocked_from_output; /** trick_io(*i) */ + int status; VSTest(); @@ -48,6 +61,8 @@ class VSTest { int success(); int fail(); + void throw_exception(); + const char *status_messages[3] = { "Variable Server Test Success", "Variable Server Test Failure", diff --git a/test/SIM_test_varserv/models/varserv/src/VS.cpp b/test/SIM_test_varserv/models/varserv/src/VS.cpp index d9d2460f..24a9aa1a 100644 --- a/test/SIM_test_varserv/models/varserv/src/VS.cpp +++ b/test/SIM_test_varserv/models/varserv/src/VS.cpp @@ -8,9 +8,11 @@ PROGRAMMERS: ( (Lindsay Landry) (L3) (9-12-2013) (Jackie Deans) (CACI) (11-30-2022) ) *******************************************************************************/ #include +#include +#include + #include "../include/VS.hh" #include "trick/exec_proto.h" -#include VSTest::VSTest() {} VSTest::~VSTest() {} @@ -40,6 +42,18 @@ int VSTest::default_vars() { for (int i = 0; i < 4000; i++) { large_arr[i] = i; } + + /* Mixed Types */ + // 3,-128,0,2112 + my_bitfield.var1 = (1 << (3 - 1)) - 1; + my_bitfield.var2 = -(1 << (8 - 1)); + my_bitfield.var3 = 0; + my_bitfield.var4 = (1 << (12 - 1)) + (1 << 12/2);; + + + blocked_from_input = 500; + blocked_from_output = 1000; + } int VSTest::init() { @@ -57,8 +71,18 @@ int VSTest::success() { return 0; } +void VSTest::throw_exception() { + throw std::logic_error("Pretend an error has occured for testing"); +} + int VSTest::shutdown() { std::cout << "Shutting down with status: " << status << " Message: " << status_messages[status] << std::endl; + + // Check that the blocked_from_input variable hasnt changed + if (blocked_from_input != 500 || blocked_from_output != 0) { + status = 1; + } + exec_terminate_with_return( status , __FILE__ , __LINE__ , status_messages[status] ) ; return(0); diff --git a/test_sims.yml b/test_sims.yml index 7f708f95..4582b819 100644 --- a/test_sims.yml +++ b/test_sims.yml @@ -312,11 +312,16 @@ SIM_test_varserv: path: test/SIM_test_varserv build_args: "-t" binary: "T_main_{cpu}_test.exe" - labels: - - retries_allowed runs: RUN_test/unit_test.py: + phase: 1 returns: 0 + RUN_test/err1_test.py: + phase: 2 + returns: 10 + RUN_test/err2_test.py: + phase: 3 + returns: 10 SIM_amoeba: path: trick_sims/Cannon/SIM_amoeba build_args: "-t" diff --git a/trick_source/sim_services/CMakeLists.txt b/trick_source/sim_services/CMakeLists.txt index 42a6e895..bce805b4 100644 --- a/trick_source/sim_services/CMakeLists.txt +++ b/trick_source/sim_services/CMakeLists.txt @@ -123,7 +123,7 @@ set( SS_SRC JITInputFile/JITInputFile JITInputFile/jit_input_file_c_intf JSONVariableServer/JSONVariableServer - JSONVariableServer/JSONVariableServerThread + JSONVariableServer/JSONVariableServerSessionThread MasterSlave/MSSharedMem MasterSlave/MSSocket MasterSlave/Master @@ -190,26 +190,26 @@ set( SS_SRC VariableServer/VariableReference VariableServer/VariableServer VariableServer/VariableServerListenThread - VariableServer/VariableServerThread - VariableServer/VariableServerThread_commands - VariableServer/VariableServerThread_connect - VariableServer/VariableServerThread_copy_data - VariableServer/VariableServerThread_copy_sim_data - VariableServer/VariableServerThread_create_socket - VariableServer/VariableServerThread_freeze_init - VariableServer/VariableServerThread_loop - VariableServer/VariableServerThread_restart - VariableServer/VariableServerThread_write_data - VariableServer/VariableServerThread_write_stdio - VariableServer/VariableServer_copy_data_freeze - VariableServer/VariableServer_copy_data_freeze_scheduled - VariableServer/VariableServer_copy_data_scheduled - VariableServer/VariableServer_copy_data_top + VariableServer/VariableServerSessionThread + VariableServer/VariableServerSessionThread_commands + VariableServer/VariableServerSessionThread_connect + VariableServer/VariableServerSessionThread_copy_data + VariableServer/VariableServerSessionThread_copy_sim_data + VariableServer/VariableServerSessionThread_create_socket + VariableServer/VariableServerSessionThread_freeze_init + VariableServer/VariableServerSessionThread_loop + VariableServer/VariableServerSessionThread_restart + VariableServer/VariableServerSessionThread_write_data + VariableServer/VariableServerSessionThread_write_stdio + VariableServer/VariableServer_copy_and_write_freeze + VariableServer/VariableServer_copy_and_write_freeze_scheduled + VariableServer/VariableServer_copy_and_write_scheduled + VariableServer/VariableServer_copy_and_write_top VariableServer/VariableServer_default_data VariableServer/VariableServer_freeze_init VariableServer/VariableServer_get_next_freeze_call_time VariableServer/VariableServer_get_next_sync_call_time - VariableServer/VariableServer_get_var_server_port + VariableServer/VariableServer_open_additional_servers VariableServer/VariableServer_init VariableServer/VariableServer_restart VariableServer/VariableServer_shutdown diff --git a/trick_source/sim_services/InputProcessor/Makefile_deps b/trick_source/sim_services/InputProcessor/Makefile_deps index b8703ada..0a9e1319 100644 --- a/trick_source/sim_services/InputProcessor/Makefile_deps +++ b/trick_source/sim_services/InputProcessor/Makefile_deps @@ -133,7 +133,7 @@ object_${TRICK_HOST_CPU}/MTV.o: MTV.cpp ${TRICK_HOME}/include/trick/MTV.hh \ ${TRICK_HOME}/include/trick/trick_byteswap.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh \ diff --git a/trick_source/sim_services/ThreadBase/SysThread.cpp b/trick_source/sim_services/ThreadBase/SysThread.cpp index a7f613e4..70484338 100644 --- a/trick_source/sim_services/ThreadBase/SysThread.cpp +++ b/trick_source/sim_services/ThreadBase/SysThread.cpp @@ -15,7 +15,6 @@ bool Trick::SysThread::shutdown_finished = false; - // Construct On First Use to avoid the Static Initialization Fiasco pthread_mutex_t& Trick::SysThread::list_mutex() { static pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -33,6 +32,12 @@ std::vector& Trick::SysThread::all_sys_threads() { } Trick::SysThread::SysThread(std::string in_name) : ThreadBase(in_name) { + pthread_mutex_init(&_restart_pause_mutex, NULL); + pthread_cond_init(&_thread_has_paused_cv, NULL); + pthread_cond_init(&_thread_wakeup_cv, NULL); + _thread_has_paused = true; + _thread_should_pause = false; + pthread_mutex_lock(&(list_mutex())); all_sys_threads().push_back(this); pthread_mutex_unlock(&(list_mutex())); @@ -65,4 +70,43 @@ int Trick::SysThread::ensureAllShutdown() { pthread_mutex_unlock(&(list_mutex())); return 0; +} + +// To be called from main thread +void Trick::SysThread::force_thread_to_pause() { + pthread_mutex_lock(&_restart_pause_mutex); + // Tell thread to pause, and wait for it to signal that it has + _thread_should_pause = true; + while (!_thread_has_paused) { + pthread_cond_wait(&_thread_has_paused_cv, &_restart_pause_mutex); + } + pthread_mutex_unlock(&_restart_pause_mutex); +} + +// To be called from main thread +void Trick::SysThread::unpause_thread() { + pthread_mutex_lock(&_restart_pause_mutex); + // Tell thread to wake up + _thread_should_pause = false; + pthread_cond_signal(&_thread_wakeup_cv); + pthread_mutex_unlock(&_restart_pause_mutex); +} + + +// To be called from this thread +void Trick::SysThread::test_pause() { + pthread_mutex_lock(&_restart_pause_mutex) ; + if (_thread_should_pause) { + // Tell main thread that we're pausing + _thread_has_paused = true; + pthread_cond_signal(&_thread_has_paused_cv); + + // Wait until we're told to wake up + while (_thread_should_pause) { + pthread_cond_wait(&_thread_wakeup_cv, &_restart_pause_mutex); + } + } + + _thread_has_paused = false; + pthread_mutex_unlock(&_restart_pause_mutex) ; } \ No newline at end of file diff --git a/trick_source/sim_services/ThreadBase/ThreadBase.cpp b/trick_source/sim_services/ThreadBase/ThreadBase.cpp index ffe6fd47..8a1a5ba0 100644 --- a/trick_source/sim_services/ThreadBase/ThreadBase.cpp +++ b/trick_source/sim_services/ThreadBase/ThreadBase.cpp @@ -288,7 +288,6 @@ int Trick::ThreadBase::create_thread() { pthread_attr_init(&attr); pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); pthread_create(&pthread_id, &attr, Trick::ThreadBase::thread_helper , (void *)this); - created = true; #if __linux diff --git a/trick_source/sim_services/VariableServer/Makefile_deps b/trick_source/sim_services/VariableServer/Makefile_deps index a4cd9fd0..8d83bab4 100644 --- a/trick_source/sim_services/VariableServer/Makefile_deps +++ b/trick_source/sim_services/VariableServer/Makefile_deps @@ -1,4 +1,4 @@ -object_${TRICK_HOST_CPU}/VariableServerThread_loop.o: VariableServerThread_loop.cpp \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_loop.o: VariableServerSessionThread_loop.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -10,7 +10,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread_loop.o: VariableServerThread_loop. ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -25,8 +25,8 @@ object_${TRICK_HOST_CPU}/VariableServerThread_loop.o: VariableServerThread_loop. ${TRICK_HOME}/include/trick/ExecutiveException.hh \ ${TRICK_HOME}/include/trick/exec_proto.h \ ${TRICK_HOME}/include/trick/sim_mode.h -object_${TRICK_HOST_CPU}/VariableServer_copy_data_scheduled.o: \ - VariableServer_copy_data_scheduled.cpp \ +object_${TRICK_HOST_CPU}/VariableServer_copy_and_write_scheduled.o: \ + VariableServer_copy_and_write_scheduled.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -38,7 +38,7 @@ object_${TRICK_HOST_CPU}/VariableServer_copy_data_scheduled.o: \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -56,13 +56,13 @@ object_${TRICK_HOST_CPU}/VariableServer_shutdown.o: VariableServer_shutdown.cpp ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh -object_${TRICK_HOST_CPU}/VariableServer_copy_data_freeze_scheduled.o: \ - VariableServer_copy_data_freeze_scheduled.cpp \ +object_${TRICK_HOST_CPU}/VariableServer_copy_and_write_freeze_scheduled.o: \ + VariableServer_copy_and_write_freeze_scheduled.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -74,7 +74,7 @@ object_${TRICK_HOST_CPU}/VariableServer_copy_data_freeze_scheduled.o: \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -92,7 +92,7 @@ object_${TRICK_HOST_CPU}/VariableReference.o: \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -116,13 +116,13 @@ object_${TRICK_HOST_CPU}/VariableServer_get_next_freeze_call_time.o: \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh \ ${TRICK_HOME}/include/trick/TrickConstant.hh -object_${TRICK_HOST_CPU}/VariableServerThread_write_stdio.o: VariableServerThread_write_stdio.cpp \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_write_stdio.o: VariableServerSessionThread_write_stdio.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -134,7 +134,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread_write_stdio.o: VariableServerThrea ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -154,7 +154,7 @@ object_${TRICK_HOST_CPU}/VariableServer_get_next_sync_call_time.o: \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -172,7 +172,7 @@ object_${TRICK_HOST_CPU}/VariableServer_restart.o: VariableServer_restart.cpp \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -192,7 +192,7 @@ object_${TRICK_HOST_CPU}/var_server_ext.o: var_server_ext.cpp \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -204,9 +204,9 @@ object_${TRICK_HOST_CPU}/var_server_ext.o: var_server_ext.cpp \ ${TRICK_HOME}/include/trick/memorymanager_c_intf.h \ ${TRICK_HOME}/include/trick/var.h \ ${TRICK_HOME}/include/trick/io_alloc.h -object_${TRICK_HOST_CPU}/VariableServerThread_create_socket.o: \ - VariableServerThread_create_socket.cpp \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_create_socket.o: \ + VariableServerSessionThread_create_socket.cpp \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ @@ -226,7 +226,7 @@ object_${TRICK_HOST_CPU}/VariableServerListenThread.o: VariableServerListenThrea ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/reference.h \ @@ -241,7 +241,7 @@ object_${TRICK_HOST_CPU}/VariableServerListenThread.o: VariableServerListenThrea ${TRICK_HOME}/include/trick/command_line_protos.h \ ${TRICK_HOME}/include/trick/message_proto.h \ ${TRICK_HOME}/include/trick/message_type.h -object_${TRICK_HOST_CPU}/VariableServerThread_write_data.o: VariableServerThread_write_data.cpp \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_write_data.o: VariableServerSessionThread_write_data.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -253,7 +253,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread_write_data.o: VariableServerThread ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -276,7 +276,7 @@ object_${TRICK_HOST_CPU}/VariableServer_init.o: VariableServer_init.cpp \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -291,7 +291,7 @@ object_${TRICK_HOST_CPU}/VariableServer_init.o: VariableServer_init.cpp \ ${TRICK_HOME}/include/trick/Threads.hh \ ${TRICK_HOME}/include/trick/ThreadTrigger.hh \ ${TRICK_HOME}/include/trick/sim_mode.h -object_${TRICK_HOST_CPU}/VariableServer_copy_data_freeze.o: VariableServer_copy_data_freeze.cpp \ +object_${TRICK_HOST_CPU}/VariableServer_copy_and_write_freeze.o: VariableServer_copy_and_write_freeze.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -303,7 +303,7 @@ object_${TRICK_HOST_CPU}/VariableServer_copy_data_freeze.o: VariableServer_copy_ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -320,7 +320,7 @@ object_${TRICK_HOST_CPU}/VariableServer_default_data.o: VariableServer_default_d ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -337,7 +337,7 @@ object_${TRICK_HOST_CPU}/VariableServer_freeze_init.o: VariableServer_freeze_ini ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -356,14 +356,14 @@ object_${TRICK_HOST_CPU}/VariableServer.o: VariableServer.cpp \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh \ ${TRICK_HOME}/include/trick/tc_proto.h -object_${TRICK_HOST_CPU}/VariableServerThread_restart.o: VariableServerThread_restart.cpp \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_restart.o: VariableServerSessionThread_restart.cpp \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ @@ -375,7 +375,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread_restart.o: VariableServerThread_re ${TRICK_HOME}/include/trick/value.h \ ${TRICK_HOME}/include/trick/dllist.h \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h -object_${TRICK_HOST_CPU}/VariableServer_copy_data_top.o: VariableServer_copy_data_top.cpp \ +object_${TRICK_HOST_CPU}/VariableServer_copy_and_write_top.o: VariableServer_copy_and_write_top.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -387,7 +387,7 @@ object_${TRICK_HOST_CPU}/VariableServer_copy_data_top.o: VariableServer_copy_dat ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -404,13 +404,13 @@ object_${TRICK_HOST_CPU}/exit_var_thread.o: exit_var_thread.cpp \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh \ ${TRICK_HOME}/include/trick/tc_proto.h -object_${TRICK_HOST_CPU}/VariableServerThread_copy_data.o: VariableServerThread_copy_data.cpp \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_copy_data.o: VariableServerSessionThread_copy_data.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -422,7 +422,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread_copy_data.o: VariableServerThread_ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -432,7 +432,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread_copy_data.o: VariableServerThread_ ${TRICK_HOME}/include/trick/realtimesync_proto.h \ ${TRICK_HOME}/include/trick/Clock.hh \ ${TRICK_HOME}/include/trick/Timer.hh -object_${TRICK_HOST_CPU}/VariableServerThread_connect.o: VariableServerThread_connect.cpp \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_connect.o: VariableServerSessionThread_connect.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -444,14 +444,14 @@ object_${TRICK_HOST_CPU}/VariableServerThread_connect.o: VariableServerThread_co ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh \ ${TRICK_HOME}/include/trick/release.h -object_${TRICK_HOST_CPU}/VariableServerThread_copy_sim_data.o: \ - VariableServerThread_copy_sim_data.cpp \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_copy_sim_data.o: \ + VariableServerSessionThread_copy_sim_data.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -463,7 +463,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread_copy_sim_data.o: \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -487,8 +487,8 @@ object_${TRICK_HOST_CPU}/VariableServerSession_freeze_init.o: VariableServerSess ${TRICK_HOME}/include/trick/dllist.h \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ ${TRICK_HOME}/include/trick/TrickConstant.hh -object_${TRICK_HOST_CPU}/VariableServer_get_var_server_port.o: \ - VariableServer_get_var_server_port.cpp \ +object_${TRICK_HOST_CPU}/VariableServer_open_additional_servers.o: \ + VariableServer_open_additional_servers.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -500,13 +500,13 @@ object_${TRICK_HOST_CPU}/VariableServer_get_var_server_port.o: \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh -object_${TRICK_HOST_CPU}/VariableServerThread.o: VariableServerThread.cpp \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread.o: VariableServerSessionThread.cpp \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ @@ -521,7 +521,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread.o: VariableServerThread.cpp \ ${TRICK_HOME}/include/trick/exec_proto.h \ ${TRICK_HOME}/include/trick/sim_mode.h \ ${TRICK_HOME}/include/trick/TrickConstant.hh -object_${TRICK_HOST_CPU}/VariableServerThread_loop.o: VariableServerThread_loop.cpp \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_loop.o: VariableServerSessionThread_loop.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -533,7 +533,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread_loop.o: VariableServerThread_loop. ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -548,8 +548,8 @@ object_${TRICK_HOST_CPU}/VariableServerThread_loop.o: VariableServerThread_loop. ${TRICK_HOME}/include/trick/ExecutiveException.hh \ ${TRICK_HOME}/include/trick/exec_proto.h \ ${TRICK_HOME}/include/trick/sim_mode.h -object_${TRICK_HOST_CPU}/VariableServer_copy_data_scheduled.o: \ - VariableServer_copy_data_scheduled.cpp \ +object_${TRICK_HOST_CPU}/VariableServer_copy_and_write_scheduled.o: \ + VariableServer_copy_and_write_scheduled.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -561,7 +561,7 @@ object_${TRICK_HOST_CPU}/VariableServer_copy_data_scheduled.o: \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -579,13 +579,13 @@ object_${TRICK_HOST_CPU}/VariableServer_shutdown.o: VariableServer_shutdown.cpp ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh -object_${TRICK_HOST_CPU}/VariableServer_copy_data_freeze_scheduled.o: \ - VariableServer_copy_data_freeze_scheduled.cpp \ +object_${TRICK_HOST_CPU}/VariableServer_copy_and_write_freeze_scheduled.o: \ + VariableServer_copy_and_write_freeze_scheduled.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -597,7 +597,7 @@ object_${TRICK_HOST_CPU}/VariableServer_copy_data_freeze_scheduled.o: \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -617,7 +617,7 @@ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -628,7 +628,7 @@ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/wcs_ext.h \ ${TRICK_HOME}/include/trick/message_proto.h \ ${TRICK_HOME}/include/trick/message_type.h -object_${TRICK_HOST_CPU}/VariableServerThread_write_stdio.o: VariableServerThread_write_stdio.cpp \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_write_stdio.o: VariableServerSessionThread_write_stdio.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -640,14 +640,14 @@ object_${TRICK_HOST_CPU}/VariableServerThread_write_stdio.o: VariableServerThrea ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh \ ${TRICK_HOME}/include/trick/variable_server_message_types.h \ ${TRICK_HOME}/include/trick/tc_proto.h -object_${TRICK_HOST_CPU}/VariableServerThread_commands.o: VariableServerThread_commands.cpp \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_commands.o: VariableServerSessionThread_commands.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -659,7 +659,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread_commands.o: VariableServerThread_c ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -691,7 +691,7 @@ object_${TRICK_HOST_CPU}/VariableServer_get_next_sync_call_time.o: \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -709,7 +709,7 @@ object_${TRICK_HOST_CPU}/VariableServer_restart.o: VariableServer_restart.cpp \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -729,7 +729,7 @@ object_${TRICK_HOST_CPU}/var_server_ext.o: var_server_ext.cpp \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -741,9 +741,9 @@ object_${TRICK_HOST_CPU}/var_server_ext.o: var_server_ext.cpp \ ${TRICK_HOME}/include/trick/memorymanager_c_intf.h \ ${TRICK_HOME}/include/trick/var.h \ ${TRICK_HOME}/include/trick/io_alloc.h -object_${TRICK_HOST_CPU}/VariableServerThread_create_socket.o: \ - VariableServerThread_create_socket.cpp \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_create_socket.o: \ + VariableServerSessionThread_create_socket.cpp \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ @@ -762,10 +762,10 @@ object_${TRICK_HOST_CPU}/VariableServerListenThread.o: VariableServerListenThrea ${TRICK_HOME}/include/trick/VariableServerListenThread.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ - ${TRICK_HOME}/include/trick/ClientListener.hh \ - ${TRICK_HOME}/include/trick/MulticastManager.hh \ + ${TRICK_HOME}/include/trick/TCPClientListener.hh \ + ${TRICK_HOME}/include/trick/MulticastGroup.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/reference.h \ @@ -780,7 +780,7 @@ object_${TRICK_HOST_CPU}/VariableServerListenThread.o: VariableServerListenThrea ${TRICK_HOME}/include/trick/command_line_protos.h \ ${TRICK_HOME}/include/trick/message_proto.h \ ${TRICK_HOME}/include/trick/message_type.h -object_${TRICK_HOST_CPU}/VariableServerThread_write_data.o: VariableServerThread_write_data.cpp \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_write_data.o: VariableServerSessionThread_write_data.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -792,7 +792,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread_write_data.o: VariableServerThread ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -815,7 +815,7 @@ object_${TRICK_HOST_CPU}/VariableServer_init.o: VariableServer_init.cpp \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -830,7 +830,7 @@ object_${TRICK_HOST_CPU}/VariableServer_init.o: VariableServer_init.cpp \ ${TRICK_HOME}/include/trick/Threads.hh \ ${TRICK_HOME}/include/trick/ThreadTrigger.hh \ ${TRICK_HOME}/include/trick/sim_mode.h -object_${TRICK_HOST_CPU}/VariableServer_copy_data_freeze.o: VariableServer_copy_data_freeze.cpp \ +object_${TRICK_HOST_CPU}/VariableServer_copy_and_write_freeze.o: VariableServer_copy_and_write_freeze.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -842,7 +842,7 @@ object_${TRICK_HOST_CPU}/VariableServer_copy_data_freeze.o: VariableServer_copy_ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -859,7 +859,7 @@ object_${TRICK_HOST_CPU}/VariableServer_default_data.o: VariableServer_default_d ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -876,7 +876,7 @@ object_${TRICK_HOST_CPU}/VariableServer_freeze_init.o: VariableServer_freeze_ini ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -895,14 +895,14 @@ object_${TRICK_HOST_CPU}/VariableServer.o: VariableServer.cpp \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh \ ${TRICK_HOME}/include/trick/tc_proto.h -object_${TRICK_HOST_CPU}/VariableServerThread_restart.o: VariableServerThread_restart.cpp \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_restart.o: VariableServerSessionThread_restart.cpp \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ @@ -914,7 +914,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread_restart.o: VariableServerThread_re ${TRICK_HOME}/include/trick/value.h \ ${TRICK_HOME}/include/trick/dllist.h \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h -object_${TRICK_HOST_CPU}/VariableServer_copy_data_top.o: VariableServer_copy_data_top.cpp \ +object_${TRICK_HOST_CPU}/VariableServer_copy_and_write_top.o: VariableServer_copy_and_write_top.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -926,7 +926,7 @@ object_${TRICK_HOST_CPU}/VariableServer_copy_data_top.o: VariableServer_copy_dat ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -943,13 +943,13 @@ object_${TRICK_HOST_CPU}/exit_var_thread.o: exit_var_thread.cpp \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh \ ${TRICK_HOME}/include/trick/tc_proto.h -object_${TRICK_HOST_CPU}/VariableServerThread_copy_data.o: VariableServerThread_copy_data.cpp \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_copy_data.o: VariableServerSessionThread_copy_data.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -961,7 +961,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread_copy_data.o: VariableServerThread_ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -984,13 +984,13 @@ object_${TRICK_HOST_CPU}/VariableServer_get_next_freeze_call_time.o: \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh \ ${TRICK_HOME}/include/trick/TrickConstant.hh -object_${TRICK_HOST_CPU}/VariableServerThread_connect.o: VariableServerThread_connect.cpp \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_connect.o: VariableServerSessionThread_connect.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -1002,14 +1002,14 @@ object_${TRICK_HOST_CPU}/VariableServerThread_connect.o: VariableServerThread_co ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh \ ${TRICK_HOME}/include/trick/release.h -object_${TRICK_HOST_CPU}/VariableServerThread_copy_sim_data.o: \ - VariableServerThread_copy_sim_data.cpp \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_copy_sim_data.o: \ + VariableServerSessionThread_copy_sim_data.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -1021,7 +1021,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread_copy_sim_data.o: \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ @@ -1031,8 +1031,8 @@ object_${TRICK_HOST_CPU}/VariableServerThread_copy_sim_data.o: \ ${TRICK_HOME}/include/trick/io_alloc.h \ ${TRICK_HOME}/include/trick/exec_proto.h \ ${TRICK_HOME}/include/trick/sim_mode.h -object_${TRICK_HOST_CPU}/VariableServerThread_freeze_init.o: VariableServerThread_freeze_init.cpp \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread_freeze_init.o: VariableServerSessionThread_freeze_init.cpp \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ @@ -1045,8 +1045,8 @@ object_${TRICK_HOST_CPU}/VariableServerThread_freeze_init.o: VariableServerThrea ${TRICK_HOME}/include/trick/dllist.h \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ ${TRICK_HOME}/include/trick/TrickConstant.hh -object_${TRICK_HOST_CPU}/VariableServer_get_var_server_port.o: \ - VariableServer_get_var_server_port.cpp \ +object_${TRICK_HOST_CPU}/VariableServer_open_additional_servers.o: \ + VariableServer_open_additional_servers.cpp \ ${TRICK_HOME}/include/trick/VariableServer.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ @@ -1058,13 +1058,13 @@ object_${TRICK_HOST_CPU}/VariableServer_get_var_server_port.o: \ ${TRICK_HOME}/include/trick/JobData.hh \ ${TRICK_HOME}/include/trick/InstrumentBase.hh \ ${TRICK_HOME}/include/trick/variable_server_sync_types.h \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ ${TRICK_HOME}/include/trick/VariableReference.hh \ ${TRICK_HOME}/include/trick/VariableServerSession.hh \ ${TRICK_HOME}/include/trick/VariableServerListenThread.hh -object_${TRICK_HOST_CPU}/VariableServerThread.o: VariableServerThread.cpp \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ +object_${TRICK_HOST_CPU}/VariableServerSessionThread.o: VariableServerSessionThread.cpp \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ @@ -1080,7 +1080,7 @@ object_${TRICK_HOST_CPU}/VariableServerThread.o: VariableServerThread.cpp \ ${TRICK_HOME}/include/trick/sim_mode.h \ ${TRICK_HOME}/include/trick/TrickConstant.hh object_${TRICK_HOST_CPU}/VariableServerSession.o: VariableServerSession.cpp \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ @@ -1098,8 +1098,8 @@ object_${TRICK_HOST_CPU}/VariableServerSession.o: VariableServerSession.cpp \ object_${TRICK_HOST_CPU}/TCConnection.o: TCConnection.cpp \ ${TRICK_HOME}/include/trick/TCConnection.hh \ ${TRICK_HOME}/include/trick/ClientConnection.hh \ - ${TRICK_HOME}/include/trick/ClientListener.hh \ - ${TRICK_HOME}/include/trick/VariableServerThread.hh \ + ${TRICK_HOME}/include/trick/TCPClientListener.hh \ + ${TRICK_HOME}/include/trick/VariableServerSessionThread.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ ${TRICK_HOME}/include/trick/ThreadBase.hh \ @@ -1115,15 +1115,15 @@ object_${TRICK_HOST_CPU}/VariableServerSession.o: VariableServerSession.cpp \ ${TRICK_HOME}/include/trick/sim_mode.h \ ${TRICK_HOME}/include/trick/TrickConstant.hh \ ${TRICK_HOME}/include/trick/tc_proto.h -object_${TRICK_HOST_CPU}/ClientListener.o: ClientListener.cpp \ +object_${TRICK_HOST_CPU}/TCPClientListener.o: TCPClientListener.cpp \ ${TRICK_HOME}/include/trick/TCPConnection.hh \ ${TRICK_HOME}/include/trick/ClientConnection.hh \ - ${TRICK_HOME}/include/trick/ClientListener.hh \ + ${TRICK_HOME}/include/trick/TCPClientListener.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ ${TRICK_HOME}/include/trick/tc_proto.h - object_${TRICK_HOST_CPU}/MulticastManager.o: MulticastManager.cpp \ - ${TRICK_HOME}/include/trick/MulticastManager.hh \ + object_${TRICK_HOST_CPU}/MulticastGroup.o: MulticastGroup.cpp \ + ${TRICK_HOME}/include/trick/MulticastGroup.hh \ ${TRICK_HOME}/include/trick/tc.h \ ${TRICK_HOME}/include/trick/trick_error_hndlr.h \ ${TRICK_HOME}/include/trick/tc_proto.h diff --git a/trick_source/sim_services/VariableServer/VariableReference.cpp b/trick_source/sim_services/VariableServer/VariableReference.cpp index ba9f97d6..a2e86a8d 100644 --- a/trick_source/sim_services/VariableServer/VariableReference.cpp +++ b/trick_source/sim_services/VariableServer/VariableReference.cpp @@ -15,19 +15,18 @@ #include "trick/UdUnits.hh" #include "trick/bitfield_proto.h" #include "trick/trick_byteswap.h" -// #include "trick/tc_proto.h" // Static variables to be addresses that are known to be the error ref address -int Trick::VariableReference::bad_ref_int = 0 ; -int Trick::VariableReference::do_not_resolve_bad_ref_int = 0 ; +int Trick::VariableReference::_bad_ref_int = 0 ; +int Trick::VariableReference::_do_not_resolve_bad_ref_int = 0 ; REF2* Trick::VariableReference::make_error_ref(std::string in_name) { REF2* new_ref; new_ref = (REF2*)calloc(1, sizeof(REF2)); new_ref->reference = strdup(in_name.c_str()) ; new_ref->units = NULL ; - new_ref->address = (char *)&bad_ref_int ; + new_ref->address = (char *)&_bad_ref_int ; new_ref->attr = (ATTRIBUTES*)calloc(1, sizeof(ATTRIBUTES)) ; new_ref->attr->type = TRICK_NUMBER_OF_TYPES ; new_ref->attr->units = (char *)"--" ; @@ -40,7 +39,7 @@ REF2* Trick::VariableReference::make_do_not_resolve_ref(std::string in_name) { new_ref = (REF2*)calloc(1, sizeof(REF2)); new_ref->reference = strdup(in_name.c_str()) ; new_ref->units = NULL ; - new_ref->address = (char *)&do_not_resolve_bad_ref_int ; + new_ref->address = (char *)&_do_not_resolve_bad_ref_int ; new_ref->attr = (ATTRIBUTES*)calloc(1, sizeof(ATTRIBUTES)) ; new_ref->attr->type = TRICK_NUMBER_OF_TYPES ; new_ref->attr->units = (char *)"--" ; @@ -62,146 +61,152 @@ REF2* make_time_ref(double * time) { return new_ref; } -Trick::VariableReference::VariableReference(std::string var_name, double* time) : staged(false), write_ready(false) { +Trick::VariableReference::VariableReference(std::string var_name, double* time) : _staged(false), _write_ready(false) { if (var_name != "time") { ASSERT(0); } - var_info = make_time_ref(time); + _var_info = make_time_ref(time); // Set up member variables - address = var_info->address; - size = var_info->attr->size ; - deref = false; + _address = _var_info->address; + _size = _var_info->attr->size ; + _deref = false; // Deal with weirdness around string vs wstring - trick_type = var_info->attr->type ; + _trick_type = _var_info->attr->type ; // Allocate stage and write buffers - stage_buffer = calloc(size, 1) ; - write_buffer = calloc(size, 1) ; + _stage_buffer = calloc(_size, 1) ; + _write_buffer = calloc(_size, 1) ; - conversion_factor = cv_get_trivial(); + _conversion_factor = cv_get_trivial(); + _base_units = _var_info->attr->units; + _requested_units = "s"; + _name = _var_info->reference; } -Trick::VariableReference::VariableReference(std::string var_name) : staged(false), write_ready(false) { +Trick::VariableReference::VariableReference(std::string var_name) : _staged(false), _write_ready(false) { if (var_name == "time") { ASSERT(0); } else { // get variable attributes from memory manager - var_info = ref_attributes(var_name.c_str()); + _var_info = ref_attributes(var_name.c_str()); } // Handle error cases - if ( var_info == NULL ) { + if ( _var_info == NULL ) { // TODO: ERROR LOGGER sendErrorMessage("Variable Server could not find variable %s.\n", var_name); // PRINTF IS NOT AN ERROR LOGGER @me printf("Variable Server could not find variable %s.\n", var_name.c_str()); - var_info = make_error_ref(var_name); - } else if ( var_info->attr ) { - if ( var_info->attr->type == TRICK_STRUCTURED ) { + _var_info = make_error_ref(var_name); + } else if ( _var_info->attr ) { + if ( _var_info->attr->type == TRICK_STRUCTURED ) { // sendErrorMessage("Variable Server: var_add cant add \"%s\" because its a composite variable.\n", var_name); printf("Variable Server: var_add cant add \"%s\" because its a composite variable.\n", var_name.c_str()); - free(var_info); - var_info = make_do_not_resolve_ref(var_name); + free(_var_info); + _var_info = make_do_not_resolve_ref(var_name); - } else if ( var_info->attr->type == TRICK_STL ) { + } else if ( _var_info->attr->type == TRICK_STL ) { // sendErrorMessage("Variable Server: var_add cant add \"%s\" because its an STL variable.\n", var_name); printf("Variable Server: var_add cant add \"%s\" because its an STL variable.\n", var_name.c_str()); - free(var_info); - var_info = make_do_not_resolve_ref(var_name); + free(_var_info); + _var_info = make_do_not_resolve_ref(var_name); } } else { // sendErrorMessage("Variable Server: BAD MOJO - Missing ATTRIBUTES."); printf("Variable Server: BAD MOJO - Missing ATTRIBUTES."); - free(var_info); - var_info = make_error_ref(var_name); + free(_var_info); + _var_info = make_error_ref(var_name); } // Set up member variables - var_info->units = NULL; - address = var_info->address; - size = var_info->attr->size ; - deref = false; + _var_info->units = NULL; + _address = _var_info->address; + _size = _var_info->attr->size ; + _deref = false; // Deal with weirdness around string vs wstring - trick_type = var_info->attr->type ; + _trick_type = _var_info->attr->type ; - if ( var_info->num_index == var_info->attr->num_index ) { + if ( _var_info->num_index == _var_info->attr->num_index ) { // single value - nothing else necessary - } else if ( var_info->attr->index[var_info->attr->num_index - 1].size != 0 ) { + } else if ( _var_info->attr->index[_var_info->attr->num_index - 1].size != 0 ) { // Constrained array - for ( int i = var_info->attr->num_index-1; i > var_info->num_index-1 ; i-- ) { - size *= var_info->attr->index[i].size ; + for ( int i = _var_info->attr->num_index-1; i > _var_info->num_index-1 ; i-- ) { + _size *= _var_info->attr->index[i].size ; } } else { // Unconstrained array - if ((var_info->attr->num_index - var_info->num_index) > 1 ) { + if ((_var_info->attr->num_index - _var_info->num_index) > 1 ) { // TODO: ERROR LOGGER - printf("Variable Server Error: var_add(%s) requests more than one dimension of dynamic array.\n", var_info->reference); + printf("Variable Server Error: var_add(%s) requests more than one dimension of dynamic array.\n", _var_info->reference); printf("Data is not contiguous so returned values are unpredictable.\n") ; } - if ( var_info->attr->type == TRICK_CHARACTER ) { - trick_type = TRICK_STRING ; - deref = true; - } else if ( var_info->attr->type == TRICK_WCHAR ) { - trick_type = TRICK_WSTRING ; - deref = true; + if ( _var_info->attr->type == TRICK_CHARACTER ) { + _trick_type = TRICK_STRING ; + _deref = true; + } else if ( _var_info->attr->type == TRICK_WCHAR ) { + _trick_type = TRICK_WSTRING ; + _deref = true; } else { - deref = true ; - size *= get_size((char*)address) ; + _deref = true ; + _size *= get_size((char*)_address) ; } } // handle strings: set a max buffer size, the copy size may vary so will be set in copy_sim_data - if (( trick_type == TRICK_STRING ) || ( trick_type == TRICK_WSTRING )) { - size = MAX_ARRAY_LENGTH ; + if (( _trick_type == TRICK_STRING ) || ( _trick_type == TRICK_WSTRING )) { + _size = MAX_ARRAY_LENGTH ; } // Allocate stage and write buffers - stage_buffer = calloc(size, 1) ; - write_buffer = calloc(size, 1) ; + _stage_buffer = calloc(_size, 1) ; + _write_buffer = calloc(_size, 1) ; - conversion_factor = cv_get_trivial(); + _conversion_factor = cv_get_trivial(); + _base_units = _var_info->attr->units; + _requested_units = ""; + _name = _var_info->reference; // Done! } Trick::VariableReference::~VariableReference() { - if (var_info != NULL) { - free( var_info ); - var_info = NULL; + if (_var_info != NULL) { + free( _var_info ); + _var_info = NULL; } - if (stage_buffer != NULL) { - free (stage_buffer); - stage_buffer = NULL; + if (_stage_buffer != NULL) { + free (_stage_buffer); + _stage_buffer = NULL; } - if (write_buffer != NULL) { - free (write_buffer); - write_buffer = NULL; + if (_write_buffer != NULL) { + free (_write_buffer); + _write_buffer = NULL; } - if (conversion_factor != NULL) { - cv_free(conversion_factor); + if (_conversion_factor != NULL) { + cv_free(_conversion_factor); } } -const char* Trick::VariableReference::getName() const { - return var_info->reference; +std::string Trick::VariableReference::getName() const { + return _name; } int Trick::VariableReference::getSizeBinary() const { - return size; + return _size; } TRICK_TYPE Trick::VariableReference::getType() const { - return trick_type; + return _trick_type; } -const char* Trick::VariableReference::getBaseUnits() const { - return var_info->attr->units; +std::string Trick::VariableReference::getBaseUnits() const { + return _base_units; } int Trick::VariableReference::setRequestedUnits(std::string units_name) { @@ -238,7 +243,7 @@ int Trick::VariableReference::setRequestedUnits(std::string units_name) { } // Interpret base unit - ut_unit * from = ut_parse(Trick::UdUnits::get_u_system(), getBaseUnits(), UT_ASCII) ; + ut_unit * from = ut_parse(Trick::UdUnits::get_u_system(), getBaseUnits().c_str(), UT_ASCII) ; if ( !from ) { std::cout << "Error in interpreting base units" << std::endl; publishError(getBaseUnits()); @@ -267,79 +272,75 @@ int Trick::VariableReference::setRequestedUnits(std::string units_name) { publish(MSG_ERROR, oss.str()); return -1 ; } else { - conversion_factor = new_conversion_factor; - } - - // Don't memory leak the old units! - if (var_info->units != NULL) { - free(var_info->units); + _conversion_factor = new_conversion_factor; } // Set the requested units. This will cause the unit string to be printed in write_value_ascii - var_info->units = strdup(new_units.c_str());; + _requested_units = new_units; } return 0; } int Trick::VariableReference::stageValue(bool validate_address) { - write_ready = false; + _write_ready = false; // Copy bytes from
to staging_point. // Try to recreate connection if it has been broken - if (var_info->address == &bad_ref_int) { - REF2 *new_ref = ref_attributes(var_info->reference); + if (_var_info->address == &_bad_ref_int) { + REF2 *new_ref = ref_attributes(_var_info->reference); if (new_ref != NULL) { - var_info = new_ref; - address = var_info->address; + _var_info = new_ref; + _address = _var_info->address; + // _requested_units = ""; } } // if there's a pointer somewhere in the address path, follow it in case pointer changed - if ( var_info->pointer_present == 1 ) { - address = follow_address_path(var_info) ; - if (address == NULL) { + if ( _var_info->pointer_present == 1 ) { + _address = follow_address_path(_var_info) ; + if (_address == NULL) { tagAsInvalid(); } else if ( validate_address ) { validate(); } else { - var_info->address = address ; + _var_info->address = _address ; } } // if this variable is a string we need to get the raw character string out of it. - if (( trick_type == TRICK_STRING ) && !deref) { - std::string * str_ptr = (std::string *)var_info->address ; + if (( _trick_type == TRICK_STRING ) && !_deref) { + std::string * str_ptr = (std::string *)_var_info->address ; // Get a pointer to the internal character array - address = (void *)(str_ptr->c_str()) ; + _address = (void *)(str_ptr->c_str()) ; } // if this variable itself is a pointer, dereference it - if ( deref ) { - address = *(void**)var_info->address ; + if ( _deref ) { + _address = *(void**)_var_info->address ; } // handle c++ string and char* - if ( trick_type == TRICK_STRING ) { - if (address == NULL) { - size = 0 ; + if ( _trick_type == TRICK_STRING ) { + if (_address == NULL) { + _size = 0 ; } else { - size = strlen((char*)address) + 1 ; + _size = strlen((char*)_address) + 1 ; } } // handle c++ wstring and wchar_t* - if ( trick_type == TRICK_WSTRING ) { - if (address == NULL) { - size = 0 ; + if ( _trick_type == TRICK_WSTRING ) { + if (_address == NULL) { + _size = 0 ; } else { - size = wcslen((wchar_t *)address) * sizeof(wchar_t); + _size = wcslen((wchar_t *)_address) * sizeof(wchar_t); } } - if(address != NULL) { - memcpy( stage_buffer , address , size ) ; + if(_address != NULL) { + memcpy( _stage_buffer , _address , _size ) ; } - staged = true; + _staged = true; return 0; } @@ -349,10 +350,10 @@ bool Trick::VariableReference::validate() { // check the memory manager if the address falls into // any of the memory blocks it knows of. Don't do this if we have a std::string or // wstring type, or we already are pointing to a bad ref. - if ( (trick_type != TRICK_STRING) and - (trick_type != TRICK_WSTRING) and - (var_info->address != &bad_ref_int) and - (get_alloc_info_of(address) == NULL) ) { + if ( (_trick_type != TRICK_STRING) and + (_trick_type != TRICK_WSTRING) and + (_var_info->address != &_bad_ref_int) and + (get_alloc_info_of(_address) == NULL) ) { // This variable is broken, make it into an error ref tagAsInvalid(); @@ -398,46 +399,41 @@ int Trick::VariableReference::getSizeAscii() const { int Trick::VariableReference::writeValueAscii( std::ostream& out ) const { // This is copied and modified from vs_format_ascii - // There's a lot here that doesn't make sense to me that I need to come back to - // There seems to be a huge buffer overflow issue in the original. - // Only strings are checked for length, arrays aren't - // But using a stream instead should make that better - // The way that arrays are handled seems weird. if (!isWriteReady()) { return -1; } int bytes_written = 0; - void * buf_ptr = write_buffer ; - while (bytes_written < size) { - bytes_written += var_info->attr->size ; + void * buf_ptr = _write_buffer ; + while (bytes_written < _size) { + bytes_written += _var_info->attr->size ; - switch (trick_type) { + switch (_trick_type) { case TRICK_CHARACTER: - if (var_info->attr->num_index == var_info->num_index) { + if (_var_info->attr->num_index == _var_info->num_index) { // Single char - out << (int)cv_convert_double(conversion_factor, *(char *)buf_ptr); + out << (int)cv_convert_double(_conversion_factor, *(char *)buf_ptr); } else { // All but last dim specified, leaves a char array write_escaped_string(out, (const char *) buf_ptr); - bytes_written = size ; + bytes_written = _size ; } break; case TRICK_UNSIGNED_CHARACTER: - if (var_info->attr->num_index == var_info->num_index) { + if (_var_info->attr->num_index == _var_info->num_index) { // Single char - out << (unsigned int)cv_convert_double(conversion_factor,*(unsigned char *)buf_ptr); + out << (unsigned int)cv_convert_double(_conversion_factor,*(unsigned char *)buf_ptr); } else { // All but last dim specified, leaves a char array write_escaped_string(out, (const char *) buf_ptr); - bytes_written = size ; + bytes_written = _size ; } break; case TRICK_WCHAR:{ - if (var_info->attr->num_index == var_info->num_index) { + if (_var_info->attr->num_index == _var_info->num_index) { out << *(wchar_t *) buf_ptr; } else { // convert wide char string char string @@ -446,7 +442,7 @@ int Trick::VariableReference::writeValueAscii( std::ostream& out ) const { char temp_buf[len]; wcs_to_ncs((wchar_t *) buf_ptr, temp_buf, len); out << temp_buf; - bytes_written = size ; + bytes_written = _size ; } } break; @@ -454,7 +450,7 @@ int Trick::VariableReference::writeValueAscii( std::ostream& out ) const { case TRICK_STRING: if ((char *) buf_ptr != NULL) { write_escaped_string(out, (const char *) buf_ptr); - bytes_written = size ; + bytes_written = _size ; } else { out << '\0'; } @@ -468,44 +464,44 @@ int Trick::VariableReference::writeValueAscii( std::ostream& out ) const { char temp_buf[len]; wcs_to_ncs( (wchar_t *) buf_ptr, temp_buf, len); out << temp_buf; - bytes_written = size ; + bytes_written = _size ; } else { out << '\0'; } break; case TRICK_SHORT: - out << (short)cv_convert_double(conversion_factor,*(short *)buf_ptr); + out << (short)cv_convert_double(_conversion_factor,*(short *)buf_ptr); break; case TRICK_UNSIGNED_SHORT: - out << (unsigned short)cv_convert_double(conversion_factor,*(unsigned short *)buf_ptr); + out << (unsigned short)cv_convert_double(_conversion_factor,*(unsigned short *)buf_ptr); break; case TRICK_INTEGER: case TRICK_ENUMERATED: - out << (int)cv_convert_double(conversion_factor,*(int *)buf_ptr); + out << (int)cv_convert_double(_conversion_factor,*(int *)buf_ptr); break; case TRICK_BOOLEAN: - out << (int)cv_convert_double(conversion_factor,*(bool *)buf_ptr); + out << (int)cv_convert_double(_conversion_factor,*(bool *)buf_ptr); break; case TRICK_BITFIELD: - out << (GET_BITFIELD(buf_ptr, var_info->attr->size, var_info->attr->index[0].start, var_info->attr->index[0].size)); + out << (GET_BITFIELD(buf_ptr, _var_info->attr->size, _var_info->attr->index[0].start, _var_info->attr->index[0].size)); break; case TRICK_UNSIGNED_BITFIELD: - out << (GET_UNSIGNED_BITFIELD(buf_ptr, var_info->attr->size, var_info->attr->index[0].start, var_info->attr->index[0].size)); + out << (GET_UNSIGNED_BITFIELD(buf_ptr, _var_info->attr->size, _var_info->attr->index[0].start, _var_info->attr->index[0].size)); break; case TRICK_UNSIGNED_INTEGER: - out << (unsigned int)cv_convert_double(conversion_factor,*(unsigned int *)buf_ptr); + out << (unsigned int)cv_convert_double(_conversion_factor,*(unsigned int *)buf_ptr); break; case TRICK_LONG: { long l = *(long *)buf_ptr; - if (conversion_factor != cv_get_trivial()) { - l = (long)cv_convert_double(conversion_factor, l); + if (_conversion_factor != cv_get_trivial()) { + l = (long)cv_convert_double(_conversion_factor, l); } out << l; break; @@ -513,25 +509,25 @@ int Trick::VariableReference::writeValueAscii( std::ostream& out ) const { case TRICK_UNSIGNED_LONG: { unsigned long ul = *(unsigned long *)buf_ptr; - if (conversion_factor != cv_get_trivial()) { - ul = (unsigned long)cv_convert_double(conversion_factor, ul); + if (_conversion_factor != cv_get_trivial()) { + ul = (unsigned long)cv_convert_double(_conversion_factor, ul); } out << ul; break; } case TRICK_FLOAT: - out << std::setprecision(8) << cv_convert_float(conversion_factor,*(float *)buf_ptr); + out << std::setprecision(8) << cv_convert_float(_conversion_factor,*(float *)buf_ptr); break; case TRICK_DOUBLE: - out << std::setprecision(16) << cv_convert_double(conversion_factor,*(double *)buf_ptr); + out << std::setprecision(16) << cv_convert_double(_conversion_factor,*(double *)buf_ptr); break; case TRICK_LONG_LONG: { long long ll = *(long long *)buf_ptr; - if (conversion_factor != cv_get_trivial()) { - ll = (long long)cv_convert_double(conversion_factor, ll); + if (_conversion_factor != cv_get_trivial()) { + ll = (long long)cv_convert_double(_conversion_factor, ll); } out << ll; break; @@ -539,8 +535,8 @@ int Trick::VariableReference::writeValueAscii( std::ostream& out ) const { case TRICK_UNSIGNED_LONG_LONG: { unsigned long long ull = *(unsigned long long *)buf_ptr; - if (conversion_factor != cv_get_trivial()) { - ull = (unsigned long long)cv_convert_double(conversion_factor, ull); + if (_conversion_factor != cv_get_trivial()) { + ull = (unsigned long long)cv_convert_double(_conversion_factor, ull); } out << ull; break; @@ -556,18 +552,18 @@ int Trick::VariableReference::writeValueAscii( std::ostream& out ) const { } } // end switch - if (bytes_written < size) { + if (bytes_written < _size) { // if returning an array, continue array as comma separated values out << ","; - buf_ptr = (void*) ((long)buf_ptr + var_info->attr->size) ; + buf_ptr = (void*) ((long)buf_ptr + _var_info->attr->size) ; } } //end while - if (var_info->units) { - if ( var_info->attr->mods & TRICK_MODS_UNITSDASHDASH ) { + if (_requested_units != "") { + if ( _var_info->attr->mods & TRICK_MODS_UNITSDASHDASH ) { out << " {--}"; } else { - out << " {" << var_info->units << "}"; + out << " {" << _requested_units << "}"; } } @@ -576,36 +572,36 @@ int Trick::VariableReference::writeValueAscii( std::ostream& out ) const { void Trick::VariableReference::tagAsInvalid () { std::string save_name(getName()) ; - free(var_info) ; - var_info = make_error_ref(save_name) ; - address = var_info->address ; + free(_var_info) ; + _var_info = make_error_ref(save_name) ; + _address = _var_info->address ; } int Trick::VariableReference::prepareForWrite() { - if (!staged) { + if (!_staged) { return 1; } - void * temp_p = stage_buffer; - stage_buffer = write_buffer; - write_buffer = temp_p; + void * temp_p = _stage_buffer; + _stage_buffer = _write_buffer; + _write_buffer = temp_p; - staged = false; - write_ready = true; + _staged = false; + _write_ready = true; return 0; } bool Trick::VariableReference::isStaged() const { - return staged; + return _staged; } bool Trick::VariableReference::isWriteReady() const { - return write_ready; + return _write_ready; } int Trick::VariableReference::writeTypeBinary( std::ostream& out, bool byteswap ) const { - int local_type = trick_type; + int local_type = _trick_type; if (byteswap) { local_type = trick_byteswap_int(local_type); } @@ -615,7 +611,7 @@ int Trick::VariableReference::writeTypeBinary( std::ostream& out, bool byteswap } int Trick::VariableReference::writeSizeBinary( std::ostream& out, bool byteswap ) const { - int local_size = size; + int local_size = _size; if (byteswap) { local_size = trick_byteswap_int(local_size); } @@ -625,15 +621,15 @@ int Trick::VariableReference::writeSizeBinary( std::ostream& out, bool byteswap } int Trick::VariableReference::writeNameBinary( std::ostream& out, bool byteswap ) const { - const char * name = getName(); + std::string name = getName(); - int name_size = strlen(name); + int name_size = name.size(); if (byteswap) { name_size = trick_byteswap_int(name_size); } out.write(const_cast(reinterpret_cast(&name_size)), sizeof(int)); - out.write(name, strlen(name)); + out.write(name.c_str(), name.size()); return 0; } @@ -644,11 +640,11 @@ void Trick::VariableReference::byteswap_var (char * out, char * in) const { void Trick::VariableReference::byteswap_var (char * out, char * in, const VariableReference& ref) { - ATTRIBUTES * attr = ref.var_info->attr; + ATTRIBUTES * attr = ref._var_info->attr; int array_size = 1; // Determine how many elements are in this array if it is an array - for (int j = 0; j < ref.var_info->attr->num_index; j++) { + for (int j = 0; j < ref._var_info->attr->num_index; j++) { array_size *= attr->index[j].size; } @@ -696,41 +692,42 @@ void Trick::VariableReference::byteswap_var (char * out, char * in, const Variab int Trick::VariableReference::writeValueBinary( std::ostream& out, bool byteswap ) const { - char buf[20480]; - int temp_i ; - unsigned int temp_ui ; - - // int offset = 0; - - switch ( var_info->attr->type ) { - case TRICK_BITFIELD: - temp_i = GET_BITFIELD(address , var_info->attr->size , - var_info->attr->index[0].start, var_info->attr->index[0].size) ; - memcpy(buf, &temp_i , (size_t)size) ; - break ; - case TRICK_UNSIGNED_BITFIELD: - temp_ui = GET_UNSIGNED_BITFIELD(address , var_info->attr->size , - var_info->attr->index[0].start, var_info->attr->index[0].size) ; - memcpy(buf , &temp_ui , (size_t)size) ; - break ; - case TRICK_NUMBER_OF_TYPES: - // TRICK_NUMBER_OF_TYPES is an error case - temp_i = 0 ; - memcpy(buf , &temp_i , (size_t)size) ; - break ; - default: - if (byteswap) - byteswap_var(buf, (char *) address); - else - memcpy(buf , address , (size_t)size) ; - break ; + if ( _trick_type == TRICK_BITFIELD ) { + int temp_i = GET_BITFIELD(_write_buffer , _var_info->attr->size , + _var_info->attr->index[0].start, _var_info->attr->index[0].size) ; + out.write((char *)(&temp_i), _size); + return _size; } - out.write(buf, size); + if ( _trick_type == TRICK_UNSIGNED_BITFIELD ) { + int temp_unsigned = GET_UNSIGNED_BITFIELD(_write_buffer , _var_info->attr->size , + _var_info->attr->index[0].start, _var_info->attr->index[0].size) ; + out.write((char *)(&temp_unsigned), _size); + return _size; + } + + if (_trick_type == TRICK_NUMBER_OF_TYPES) { + // TRICK_NUMBER_OF_TYPES is an error case + int temp_zero = 0 ; + out.write((char *)(&temp_zero), _size); + return _size; + } + + if (byteswap) { + char * byteswap_buf = (char *) calloc (_size, 1); + byteswap_var(byteswap_buf, (char *) _write_buffer); + out.write(byteswap_buf, _size); + free (byteswap_buf); + } + else { + out.write((char *) _write_buffer, _size); + } + + return _size; + } std::ostream& Trick::operator<< (std::ostream& s, const Trick::VariableReference& ref) { - s << " \"" << ref.getName() << "\""; return s; } \ No newline at end of file diff --git a/trick_source/sim_services/VariableServer/VariableServer.cpp b/trick_source/sim_services/VariableServer/VariableServer.cpp index 01c3db97..2800778c 100644 --- a/trick_source/sim_services/VariableServer/VariableServer.cpp +++ b/trick_source/sim_services/VariableServer/VariableServer.cpp @@ -16,11 +16,11 @@ Trick::VariableServer::VariableServer() : } Trick::VariableServer::~VariableServer() { - + the_vs = NULL; } std::ostream& Trick::operator<< (std::ostream& s, Trick::VariableServer& vs) { - std::map < pthread_t , VariableServerThread * >::iterator it ; + std::map < pthread_t , VariableServerSessionThread * >::iterator it ; s << "{\"variable_server_connections\":[\n"; int count = 0; @@ -66,31 +66,28 @@ bool Trick::VariableServer::get_log() { void Trick::VariableServer::set_var_server_log_on() { log = true; // turn log on for all current vs clients - std::map < pthread_t , VariableServerSession * >::iterator it ; - for ( it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { - (*it).second->set_log_on(); + for ( auto& session_it : var_server_sessions ) { + session_it.second->set_log_on(); } } void Trick::VariableServer::set_var_server_log_off() { log = false; // turn log off for all current vs clients - std::map < pthread_t , VariableServerSession * >::iterator it ; - for ( it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { - (*it).second->set_log_off(); + for ( auto& session_it : var_server_sessions ) { + session_it.second->set_log_off(); } } const char * Trick::VariableServer::get_hostname() { - const char * ret = (listen_thread.get_hostname()) ; - return ret; + return listen_thread.get_hostname(); } Trick::VariableServerListenThread & Trick::VariableServer::get_listen_thread() { return listen_thread ; } -void Trick::VariableServer::add_vst(pthread_t in_thread_id, VariableServerThread * in_vst) { +void Trick::VariableServer::add_vst(pthread_t in_thread_id, VariableServerSessionThread * in_vst) { pthread_mutex_lock(&map_mutex) ; var_server_threads[in_thread_id] = in_vst ; pthread_mutex_unlock(&map_mutex) ; @@ -102,9 +99,9 @@ void Trick::VariableServer::add_session(pthread_t in_thread_id, VariableServerSe pthread_mutex_unlock(&map_mutex) ; } -Trick::VariableServerThread * Trick::VariableServer::get_vst(pthread_t thread_id) { - std::map < pthread_t , Trick::VariableServerThread * >::iterator it ; - Trick::VariableServerThread * ret = NULL ; +Trick::VariableServerSessionThread * Trick::VariableServer::get_vst(pthread_t thread_id) { + std::map < pthread_t , Trick::VariableServerSessionThread * >::iterator it ; + Trick::VariableServerSessionThread * ret = NULL ; pthread_mutex_lock(&map_mutex) ; it = var_server_threads.find(thread_id) ; if ( it != var_server_threads.end() ) { @@ -115,10 +112,9 @@ Trick::VariableServerThread * Trick::VariableServer::get_vst(pthread_t thread_id } Trick::VariableServerSession * Trick::VariableServer::get_session(pthread_t thread_id) { - std::map < pthread_t , Trick::VariableServerSession * >::iterator it ; Trick::VariableServerSession * ret = NULL ; pthread_mutex_lock(&map_mutex) ; - it = var_server_sessions.find(thread_id) ; + auto it = var_server_sessions.find(thread_id) ; if ( it != var_server_sessions.end() ) { ret = (*it).second ; } @@ -142,6 +138,6 @@ void Trick::VariableServer::set_copy_data_job( Trick::JobData * in_job ) { copy_data_job = in_job ; } -void Trick::VariableServer::set_copy_data_freeze_job( Trick::JobData * in_job ) { - copy_data_freeze_job = in_job ; +void Trick::VariableServer::set_copy_and_write_freeze_job( Trick::JobData * in_job ) { + copy_and_write_freeze_job = in_job ; } diff --git a/trick_source/sim_services/VariableServer/VariableServerListenThread.cpp b/trick_source/sim_services/VariableServer/VariableServerListenThread.cpp index c5ea2163..771cfea6 100644 --- a/trick_source/sim_services/VariableServer/VariableServerListenThread.cpp +++ b/trick_source/sim_services/VariableServer/VariableServerListenThread.cpp @@ -3,8 +3,7 @@ #include #include "trick/VariableServerListenThread.hh" -#include "trick/VariableServerThread.hh" -#include "trick/tc_proto.h" +#include "trick/VariableServerSessionThread.hh" #include "trick/exec_proto.h" #include "trick/command_line_protos.h" #include "trick/message_proto.h" @@ -12,32 +11,44 @@ #define MAX_MACHINE_NAME 80 -Trick::VariableServerListenThread::VariableServerListenThread() : - Trick::SysThread("VarServListen"), - requested_port(0), - user_requested_address(false), - broadcast(true), - listener() -{ - pthread_mutex_init(&restart_pause, NULL); - char hname[MAX_MACHINE_NAME]; - gethostname(hname, MAX_MACHINE_NAME); - requested_source_address = std::string(hname); +Trick::VariableServerListenThread::VariableServerListenThread() : VariableServerListenThread (NULL) {} + +Trick::VariableServerListenThread::VariableServerListenThread(TCPClientListener * listener) : + Trick::SysThread("VarServListen"), + _requested_source_address(""), + _requested_port(0), + _user_requested_address(false), + _broadcast(true), + _listener(listener), + _multicast(new MulticastGroup()) +{ + if (_listener != NULL) { + // If we were passed a listener + // We assume it is already initialized + _requested_source_address = _listener->getHostname(); + _requested_port = _listener->getPort(); + _user_requested_address = true; + } else { + // Otherwise, make one + _listener = new TCPClientListener; + } cancellable = false; } Trick::VariableServerListenThread::~VariableServerListenThread() { - // if (multicast != NULL) { - // delete multicast; - // multicast = NULL; - // } + delete _listener; + delete _multicast; } +void Trick::VariableServerListenThread::set_multicast_group (MulticastGroup * group) { + delete _multicast; + _multicast = group; +} const char * Trick::VariableServerListenThread::get_hostname() { - std::string hostname = listener.getHostname(); + std::string hostname = _requested_source_address; char * ret = (char *) malloc(hostname.length() + 1); strncpy(ret, hostname.c_str(), hostname.length()); ret[hostname.length()] = '\0'; @@ -45,80 +56,74 @@ const char * Trick::VariableServerListenThread::get_hostname() { } unsigned short Trick::VariableServerListenThread::get_port() { - return requested_port; + return _requested_port; } void Trick::VariableServerListenThread::set_port(unsigned short in_port) { - requested_port = in_port; - user_requested_address = true ; + _requested_port = in_port; + _user_requested_address = true ; } std::string Trick::VariableServerListenThread::get_user_tag() { - return user_tag ; + return _user_tag ; } const std::string& Trick::VariableServerListenThread::get_user_tag_ref() { - return user_tag ; + return _user_tag ; } void Trick::VariableServerListenThread::set_user_tag(std::string in_tag) { - user_tag = in_tag ; + _user_tag = in_tag ; } void Trick::VariableServerListenThread::set_source_address(const char * address) { if ( address == NULL ) { - requested_source_address = std::string("") ; + _requested_source_address = std::string("") ; } else { - requested_source_address = std::string(address) ; + _requested_source_address = std::string(address) ; } - user_requested_address = true ; + _user_requested_address = true; } std::string Trick::VariableServerListenThread::get_source_address() { - return listener.getHostname() ; + return _requested_source_address ; } bool Trick::VariableServerListenThread::get_broadcast() { - return broadcast; + return _broadcast; } -// in_broadcast atomic? We'll see what tsan says. Maybe go nuts with atomics void Trick::VariableServerListenThread::set_broadcast(bool in_broadcast) { - broadcast = in_broadcast; + _broadcast = in_broadcast; } +// Called from default data int Trick::VariableServerListenThread::init_listen_device() { - int ret = listener.initialize(); - requested_port = listener.getPort(); - user_requested_address = true; + int ret = _listener->initialize(); + _requested_port = _listener->getPort(); + _requested_source_address = _listener->getHostname(); return ret; } +// Called from init jobs int Trick::VariableServerListenThread::check_and_move_listen_device() { int ret ; - /* The user has requested a different source address or port in the input file */ - listener.disconnect(); - ret = listener.initialize(requested_source_address, requested_port); - requested_port = listener.getPort(); - requested_source_address = listener.getHostname(); - if (ret != 0) { - message_publish(MSG_ERROR, "ERROR: Could not establish variable server source_address %s: port %d. Aborting.\n", - requested_source_address.c_str(), requested_port); - return -1 ; + if (_user_requested_address) { + /* The user has requested a different source address or port in the input file */ + _listener->disconnect(); + ret = _listener->initialize(_requested_source_address, _requested_port); + _requested_port = _listener->getPort(); + _requested_source_address = _listener->getHostname(); + if (ret != 0) { + message_publish(MSG_ERROR, "ERROR: Could not establish variable server source_address %s: port %d. Aborting.\n", + _requested_source_address.c_str(), _requested_port); + return -1 ; + } } return 0 ; } -void Trick::VariableServerListenThread::create_tcp_socket(const char * address, unsigned short in_port ) { - listener.initialize(address, in_port); - requested_source_address = listener.getHostname(); - requested_port = listener.getPort(); - user_requested_address = true; - - message_publish(MSG_INFO, "Created TCP variable server %s: %d\n", requested_source_address.c_str(), in_port); -} - void * Trick::VariableServerListenThread::thread_body() { // This thread listens for incoming client connections, and when one is received, creates a new thread to handle the session // Also broadcasts on multicast channel @@ -129,7 +134,7 @@ void * Trick::VariableServerListenThread::thread_body() { std::string version = std::string(exec_get_current_version()) ; version.erase(version.find_last_not_of(" \t\f\v\n\r")+1); - // get username to broadcast on multicast channel + // get username to _broadcast on multicast channel struct passwd *passp = getpwuid(getuid()) ; std::string user_name; if ( passp == NULL ) { @@ -138,9 +143,9 @@ void * Trick::VariableServerListenThread::thread_body() { user_name = strdup(passp->pw_name) ; } - listener.setBlockMode(true); + _listener->setBlockMode(true); - if ( broadcast ) { + if ( _broadcast ) { initializeMulticast(); } @@ -148,20 +153,15 @@ void * Trick::VariableServerListenThread::thread_body() { // Quit here if it's time test_shutdown(); - // Look for a new client requesting a connection - if (listener.checkForNewConnections()) { - // pause here during restart - pthread_mutex_lock(&restart_pause) ; + // Pause here if we need to + test_pause(); - // Recheck - sometimes we get false positive if something happens during restart - if (!listener.checkForNewConnections()) { - pthread_mutex_unlock(&restart_pause) ; - continue; - } + // Look for a new client requesting a connection + if (_listener->checkForNewConnections()) { // Create a new thread to service this connection - VariableServerThread * vst = new Trick::VariableServerThread() ; - vst->open_tcp_connection(&listener) ; + VariableServerSessionThread * vst = new Trick::VariableServerSessionThread() ; + vst->set_connection(_listener->setUpNewConnection()); vst->copy_cpus(get_cpus()) ; vst->create_thread() ; ConnectionStatus status = vst->wait_for_accept() ; @@ -172,23 +172,20 @@ void * Trick::VariableServerListenThread::thread_body() { vst->join_thread(); delete vst; } - - pthread_mutex_unlock(&restart_pause) ; - - } else if ( broadcast ) { + } else if ( _broadcast ) { // Otherwise, broadcast on the multicast channel if enabled char buf1[1024]; - sprintf(buf1 , "%s\t%hu\t%s\t%d\t%s\t%s\t%s\t%s\t%s\t%hu\n" , listener.getHostname(), (unsigned short)listener.getPort() , + snprintf(buf1 , sizeof(buf1), "%s\t%hu\t%s\t%d\t%s\t%s\t%s\t%s\t%s\t%hu\n" , _listener->getHostname().c_str(), (unsigned short)_listener->getPort() , user_name.c_str() , (int)getpid() , command_line_args_get_default_dir() , command_line_args_get_cmdline_name() , - command_line_args_get_input_file() , version.c_str() , user_tag.c_str(), (unsigned short)listener.getPort() ) ; + command_line_args_get_input_file() , version.c_str() , _user_tag.c_str(), (unsigned short)_listener->getPort() ) ; - std::string message = buf1; + std::string message(buf1); - if (!multicast.is_initialized()) { + if (!_multicast->isInitialized()) { // In case broadcast was turned on after this loop was entered initializeMulticast(); } - multicast.broadcast(message); + _multicast->broadcast(message); } } @@ -201,26 +198,27 @@ int Trick::VariableServerListenThread::restart() { int ret ; - listener.restart(); + _listener->restart(); - if ( user_requested_address ) { - - if (!listener.validateSourceAddress(requested_source_address)) { - requested_source_address.clear() ; + if ( _user_requested_address ) { + // If the use requested an address and/or port, make sure we reinitialize to the same one + if (!_listener->validateSourceAddress(_requested_source_address)) { + _requested_source_address.clear() ; } - printf("variable server restart user_port requested set %s:%d\n",requested_source_address.c_str(), requested_port); + printf("variable server restart user_port requested set %s:%d\n",_requested_source_address.c_str(), _requested_port); - listener.disconnect(); - ret = listener.initialize(requested_source_address, requested_port); + _listener->disconnect(); + ret = _listener->initialize(_requested_source_address, _requested_port); if (ret != TC_SUCCESS) { - message_publish(MSG_ERROR, "ERROR: Could not establish listen port %d for Variable Server. Aborting.\n", requested_port); + message_publish(MSG_ERROR, "ERROR: Could not establish listen port %d for Variable Server. Aborting.\n", _requested_port); return (-1); } } else { - listener.checkSocket(); - printf("restart variable server message port = %d\n", listener.getPort()); + // Otherwise, just ask the listener what port it's using + _listener->checkSocket(); + printf("restart variable server message port = %d\n", _listener->getPort()); } initializeMulticast(); @@ -229,27 +227,29 @@ int Trick::VariableServerListenThread::restart() { } void Trick::VariableServerListenThread::initializeMulticast() { - multicast.initialize(); - multicast.addAddress("239.3.14.15", 9265); - multicast.addAddress("224.3.14.15", 9265); + _multicast->initialize(); + _multicast->addAddress("239.3.14.15", 9265); + _multicast->addAddress("224.3.14.15", 9265); } void Trick::VariableServerListenThread::pause_listening() { - pthread_mutex_lock(&restart_pause) ; + // pthread_mutex_lock(&_restart_pause) ; + force_thread_to_pause(); } void Trick::VariableServerListenThread::restart_listening() { - listener.restart(); - pthread_mutex_unlock(&restart_pause) ; + _listener->restart(); + unpause_thread(); + // pthread_mutex_unlock(&_restart_pause) ; } void Trick::VariableServerListenThread::dump( std::ostream & oss ) { oss << "Trick::VariableServerListenThread (" << name << ")" << std::endl ; - oss << " source_address = " << listener.getHostname() << std::endl ; - oss << " port = " << listener.getPort() << std::endl ; - oss << " user_requested_address = " << user_requested_address << std::endl ; - oss << " user_tag = " << user_tag << std::endl ; - oss << " broadcast = " << broadcast << std::endl ; + oss << " source_address = " << _listener->getHostname() << std::endl ; + oss << " port = " << _listener->getPort() << std::endl ; + oss << " user_requested_address = " << _user_requested_address << std::endl ; + oss << " user_tag = " << _user_tag << std::endl ; + oss << " broadcast = " << _broadcast << std::endl ; Trick::ThreadBase::dump(oss) ; } diff --git a/trick_source/sim_services/VariableServer/VariableServerSession.cpp b/trick_source/sim_services/VariableServer/VariableServerSession.cpp index 32c03932..f40d9557 100644 --- a/trick_source/sim_services/VariableServer/VariableServerSession.cpp +++ b/trick_source/sim_services/VariableServer/VariableServerSession.cpp @@ -6,94 +6,116 @@ #include "trick/realtimesync_proto.h" -Trick::VariableServerSession::VariableServerSession(ClientConnection * conn) { - debug = 0; - enabled = true ; - log = false ; - copy_mode = VS_COPY_ASYNC ; - write_mode = VS_WRITE_ASYNC ; - frame_multiple = 1 ; - frame_offset = 0 ; - freeze_frame_multiple = 1 ; - freeze_frame_offset = 0 ; - update_rate = 0.1 ; - cycle_tics = (long long)(update_rate * exec_get_time_tic_value()) ; - if (cycle_tics == 0) { - cycle_tics = 1; +Trick::VariableServerSession::VariableServerSession() { + _debug = 0; + _enabled = true ; + _log = false ; + _copy_mode = VS_COPY_ASYNC ; + _write_mode = VS_WRITE_ASYNC ; + _frame_multiple = 1 ; + _frame_offset = 0 ; + _freeze_frame_multiple = 1 ; + _freeze_frame_offset = 0 ; + _update_rate = 0.1 ; + _cycle_tics = (long long)(_update_rate * exec_get_time_tic_value()) ; + if (_cycle_tics == 0) { + _cycle_tics = 1; } - next_tics = TRICK_MAX_LONG_LONG ; - freeze_next_tics = TRICK_MAX_LONG_LONG ; - // multicast = false; - connection = conn; - byteswap = false ; - validate_address = false ; - send_stdio = false ; + _next_tics = TRICK_MAX_LONG_LONG ; + _freeze_next_tics = TRICK_MAX_LONG_LONG ; + _byteswap = false ; + _validate_address = false ; + _send_stdio = false ; - binary_data = false; - byteswap = false; - binary_data_nonames = false; - packets_copied = 0 ; + _binary_data = false; + _byteswap = false; + _binary_data_nonames = false; - exit_cmd = false; - pause_cmd = false; + _exit_cmd = false; + _pause_cmd = false; - - // incoming_msg = (char *) calloc(MAX_CMD_LEN, 1); - // stripped_msg = (char *) calloc(MAX_CMD_LEN, 1); - - - pthread_mutex_init(©_mutex, NULL); + pthread_mutex_init(&_copy_mutex, NULL); } Trick::VariableServerSession::~VariableServerSession() { - // free (incoming_msg); - // free (stripped_msg); + for (unsigned int ii = 0 ; ii < _session_variables.size() ; ii++ ) { + delete _session_variables[ii]; + } + } + + +void Trick::VariableServerSession::set_connection(ClientConnection * conn) { + _connection = conn; } + // Command to turn on log to varserver_log file int Trick::VariableServerSession::set_log_on() { - log = true; + _log = true; return(0) ; } // Command to turn off log to varserver_log file int Trick::VariableServerSession::set_log_off() { - log = false; + _log = false; return(0) ; } +bool Trick::VariableServerSession::get_pause() { + return _pause_cmd ; +} + +void Trick::VariableServerSession::set_pause( bool on_off) { + _pause_cmd = on_off ; +} + + +bool Trick::VariableServerSession::get_exit_cmd() { + return _exit_cmd ; +} + +void Trick::VariableServerSession::pause_copy() { + pthread_mutex_lock(&_copy_mutex); +} + +void Trick::VariableServerSession::unpause_copy() { + pthread_mutex_unlock(&_copy_mutex); +} + void Trick::VariableServerSession::disconnect_references() { - for (VariableReference * variable : session_variables) { + for (VariableReference * variable : _session_variables) { variable->tagAsInvalid(); } } long long Trick::VariableServerSession::get_next_tics() const { - if ( ! enabled ) { + if ( ! _enabled ) { return TRICK_MAX_LONG_LONG ; } - return next_tics ; + return _next_tics ; } long long Trick::VariableServerSession::get_freeze_next_tics() const { - if ( ! enabled ) { + if ( ! _enabled ) { return TRICK_MAX_LONG_LONG ; } - return freeze_next_tics ; + return _freeze_next_tics ; } -int Trick::VariableServerSession::handleMessage() { +int Trick::VariableServerSession::handle_message() { - std::string received_message = connection->read(ClientConnection::MAX_CMD_LEN); - if (received_message.size() > 0) { + std::string received_message; + int nbytes = _connection->read(received_message); + if (nbytes > 0) { ip_parse(received_message.c_str()); /* returns 0 if no parsing error */ } - + + return nbytes; } Trick::VariableReference * Trick::VariableServerSession::find_session_variable(std::string name) const { - for (VariableReference * ref : session_variables) { + for (VariableReference * ref : _session_variables) { // Look for matching name if (name.compare(ref->getName()) == 0) { return ref; @@ -104,19 +126,57 @@ Trick::VariableReference * Trick::VariableServerSession::find_session_variable(s } double Trick::VariableServerSession::get_update_rate() const { - return update_rate; + return _update_rate; } VS_WRITE_MODE Trick::VariableServerSession::get_write_mode () const { - return write_mode; + return _write_mode; } VS_COPY_MODE Trick::VariableServerSession::get_copy_mode () const { - return copy_mode; + return _copy_mode; } +long long Trick::VariableServerSession::get_cycle_tics() const { + return _cycle_tics; +} + +int Trick::VariableServerSession::get_frame_multiple () const { + return _frame_multiple; +} + +int Trick::VariableServerSession::get_frame_offset () const { + return _frame_offset; +} + +int Trick::VariableServerSession::get_freeze_frame_multiple () const { + return _freeze_frame_multiple; +} + +int Trick::VariableServerSession::get_freeze_frame_offset () const { + return _freeze_frame_offset; +} + +bool Trick::VariableServerSession::get_enabled () const { + return _enabled; +} + +void Trick::VariableServerSession::set_freeze_next_tics(long long tics) { + _freeze_next_tics = tics; +} + +void Trick::VariableServerSession::set_next_tics(long long tics) { + _next_tics = tics; +} + +void Trick::VariableServerSession::set_exit_cmd() { + _exit_cmd = true; +} + + + std::ostream& Trick::operator<< (std::ostream& s, const Trick::VariableServerSession& session) { - if (session.binary_data) { + if (session._binary_data) { s << " \"format\":\"BINARY\",\n"; } else { s << " \"format\":\"ASCII\",\n"; @@ -125,9 +185,9 @@ std::ostream& Trick::operator<< (std::ostream& s, const Trick::VariableServerSes s << " \"variables\":[\n"; - int n_vars = (int)session.session_variables.size(); + int n_vars = (int)session._session_variables.size(); for (int i=0 ; i1) { s << "," ; } diff --git a/trick_source/sim_services/VariableServer/VariableServerSessionThread.cpp b/trick_source/sim_services/VariableServer/VariableServerSessionThread.cpp new file mode 100644 index 00000000..a1108d23 --- /dev/null +++ b/trick_source/sim_services/VariableServer/VariableServerSessionThread.cpp @@ -0,0 +1,134 @@ + +#include +#include +#include "trick/VariableServerSessionThread.hh" +#include "trick/exec_proto.h" +#include "trick/message_proto.h" +#include "trick/message_type.h" +#include "trick/TrickConstant.hh" +#include "trick/UDPConnection.hh" +#include "trick/TCPConnection.hh" + + +Trick::VariableServer * Trick::VariableServerSessionThread::_vs = NULL ; + +static int instance_num = 0; + +Trick::VariableServerSessionThread::VariableServerSessionThread() : VariableServerSessionThread (new VariableServerSession()) {} + +Trick::VariableServerSessionThread::VariableServerSessionThread(VariableServerSession * session) : + Trick::SysThread(std::string("VarServer" + std::to_string(instance_num++))) , _debug(0), _session(session), _connection(NULL) { + + _connection_status = CONNECTION_PENDING ; + + + pthread_mutex_init(&_connection_status_mutex, NULL); + pthread_cond_init(&_connection_status_cv, NULL); + + cancellable = false; +} + +Trick::VariableServerSessionThread::~VariableServerSessionThread() { + cleanup(); +} + +std::ostream& Trick::operator<< (std::ostream& s, Trick::VariableServerSessionThread& vst) { + // Write a JSON representation of a Trick::VariableServerSessionThread to an ostream. + s << " \"connection\":{\n"; + s << " \"client_tag\":\"" << vst._connection->getClientTag() << "\",\n"; + + s << " \"client_IP_address\":\"" << vst._connection->getClientHostname() << "\",\n"; + s << " \"client_port\":\"" << vst._connection->getClientPort() << "\",\n"; + + pthread_mutex_lock(&vst._connection_status_mutex); + if (vst._connection_status == CONNECTION_SUCCESS) { + s << *(vst._session); + } + pthread_mutex_unlock(&vst._connection_status_mutex); + + s << " }" << std::endl; + return s; +} + +void Trick::VariableServerSessionThread::set_vs_ptr(Trick::VariableServer * in_vs) { + _vs = in_vs ; +} + +Trick::VariableServer * Trick::VariableServerSessionThread::get_vs() { + return _vs ; +} + +void Trick::VariableServerSessionThread::set_client_tag(std::string tag) { + _connection->setClientTag(tag); +} + +void Trick::VariableServerSessionThread::set_connection(Trick::ClientConnection * in_connection) { + _connection = in_connection; +} + +Trick::ConnectionStatus Trick::VariableServerSessionThread::wait_for_accept() { + + pthread_mutex_lock(&_connection_status_mutex); + while ( _connection_status == CONNECTION_PENDING ) { + pthread_cond_wait(&_connection_status_cv, &_connection_status_mutex); + } + pthread_mutex_unlock(&_connection_status_mutex); + + return _connection_status; +} + +// Gets called from the main thread as a job +void Trick::VariableServerSessionThread::preload_checkpoint() { + + // Stop variable server processing at the top of the processing loop. + force_thread_to_pause(); + + + // Make sure that the _session has been initialized + pthread_mutex_lock(&_connection_status_mutex); + if (_connection_status == CONNECTION_SUCCESS) { + + // Let the thread complete any data copying it has to do + // and then suspend data copying until the checkpoint is reloaded. + _session->pause_copy(); + + // Save the pause state of this thread. + _saved_pause_cmd = _session->get_pause(); + + // Disallow data writing. + _session->set_pause(true); + + // Temporarily "disconnect" the variable references from Trick Managed Memory + // by tagging each as a "bad reference". + _session->disconnect_references(); + + // Allow data copying to continue. + _session->unpause_copy(); + } + pthread_mutex_unlock(&_connection_status_mutex); +} + +// Gets called from the main thread as a job +void Trick::VariableServerSessionThread::restart() { + // Set the pause state of this thread back to its "pre-checkpoint reload" state. + _connection->restart(); + + pthread_mutex_lock(&_connection_status_mutex); + if (_connection_status == CONNECTION_SUCCESS) { + _session->set_pause(_saved_pause_cmd) ; + } + pthread_mutex_unlock(&_connection_status_mutex); + + // Restart the variable server processing. + unpause_thread(); +} + +void Trick::VariableServerSessionThread::cleanup() { + _connection->disconnect(); + + if (_session != NULL) { + delete _session; + _session = NULL; + } +} + diff --git a/trick_source/sim_services/VariableServer/VariableServerThread_loop.cpp b/trick_source/sim_services/VariableServer/VariableServerSessionThread_loop.cpp similarity index 50% rename from trick_source/sim_services/VariableServer/VariableServerThread_loop.cpp rename to trick_source/sim_services/VariableServer/VariableServerSessionThread_loop.cpp index 7c68d08d..b0126daf 100644 --- a/trick_source/sim_services/VariableServer/VariableServerThread_loop.cpp +++ b/trick_source/sim_services/VariableServer/VariableServerSessionThread_loop.cpp @@ -1,65 +1,58 @@ -#include -#include #include #ifdef __linux #include #endif -#include - #include "trick/VariableServer.hh" -#include "trick/variable_server_sync_types.h" -#include "trick/input_processor_proto.h" -#include "trick/tc_proto.h" #include "trick/message_proto.h" -#include "trick/message_type.h" #include "trick/realtimesync_proto.h" #include "trick/ExecutiveException.hh" #include "trick/exec_proto.h" +#include "trick/VariableServerSessionThread.hh" + void exit_var_thread(void *in_vst) ; -void * Trick::VariableServerThread::thread_body() { +void * Trick::VariableServerSessionThread::thread_body() { // Check for short running sims test_shutdown(NULL, NULL); - // We need to make the thread to VariableServerThread map before we accept the connection. + // We need to make the thread to VariableServerSessionThread map before we accept the connection. // Otherwise we have a race where this thread is unknown to the variable server and the // client gets confirmation that the connection is ready for communication. - vs->add_vst( pthread_self() , this ) ; + _vs->add_vst( pthread_self() , this ) ; // Accept client connection - int status = connection->start(); + int status = _connection->start(); if (status != 0) { - // TODO: Use a real error handler - vs->delete_vst(pthread_self()); + _vs->delete_vst(pthread_self()); // Tell main thread that we failed to initialize - pthread_mutex_lock(&connection_status_mutex); - connection_status = CONNECTION_FAIL; - pthread_cond_signal(&connection_status_cv); - pthread_mutex_unlock(&connection_status_mutex); + pthread_mutex_lock(&_connection_status_mutex); + _connection_status = CONNECTION_FAIL; + pthread_cond_signal(&_connection_status_cv); + pthread_mutex_unlock(&_connection_status_mutex); - cleanup(); - pthread_exit(NULL); + thread_shutdown(); } - // Create session - session = new VariableServerSession(connection); - vs->add_session( pthread_self(), session ); + // Give the initialized connection to the session + // Don't touch the connection anymore until we shut them both down + _session->set_connection(_connection); + _vs->add_session( pthread_self(), _session ); // Tell main that we are ready - pthread_mutex_lock(&connection_status_mutex); - connection_status = CONNECTION_SUCCESS; - pthread_cond_signal(&connection_status_cv); - pthread_mutex_unlock(&connection_status_mutex); + pthread_mutex_lock(&_connection_status_mutex); + _connection_status = CONNECTION_SUCCESS; + pthread_cond_signal(&_connection_status_cv); + pthread_mutex_unlock(&_connection_status_mutex); // if log is set on for variable server (e.g., in input file), turn log on for each client - if (vs->get_log()) { - session->set_log_on(); + if (_vs->get_log()) { + _session->set_log_on(); } try { @@ -68,47 +61,41 @@ void * Trick::VariableServerThread::thread_body() { test_shutdown(exit_var_thread, (void *) this); // Pause here if we are in a restart condition - pthread_mutex_lock(&restart_pause) ; + test_pause(); // Look for a message from the client // Parse and execute if one is availible - session->handleMessage(); + int read_status = _session->handle_message(); + if ( read_status < 0 ) { + break ; + } // Check to see if exit is necessary - if (session->exit_cmd == true) { - pthread_mutex_unlock(&restart_pause) ; + if (_session->get_exit_cmd() == true) { break; } - // Copy data out of sim if async mode - if ( session->get_copy_mode() == VS_COPY_ASYNC ) { - session->copy_sim_data() ; + // Tell session it's time to copy and write if the mode is correct + int ret =_session->copy_and_write_async(); + if (ret < 0) { + break; } - - bool should_write_async = (session->get_write_mode() == VS_WRITE_ASYNC) || - ( session->get_copy_mode() == VS_COPY_ASYNC && (session->get_write_mode() == VS_WRITE_WHEN_COPIED)) || - (! is_real_time()); - // Write data out to connection if async mode and not paused - if ( should_write_async && !session->get_pause()) { - int ret = session->write_data() ; - if ( ret < 0 ) { - pthread_mutex_unlock(&restart_pause) ; - break ; - } - } - pthread_mutex_unlock(&restart_pause) ; - - usleep((unsigned int) (session->get_update_rate() * 1000000)); + // Sleep for the appropriate cycle time + usleep((unsigned int) (_session->get_update_rate() * 1000000)); } } catch (Trick::ExecutiveException & ex ) { message_publish(MSG_ERROR, "\nVARIABLE SERVER COMMANDED exec_terminate\n ROUTINE: %s\n DIAGNOSTIC: %s\n" , ex.file.c_str(), ex.message.c_str()) ; - exit(ex.ret_code) ; + + exec_signal_terminate(); + } catch (const std::exception &ex) { message_publish(MSG_ERROR, "\nVARIABLE SERVER caught std::exception\n DIAGNOSTIC: %s\n" , ex.what()) ; - exit(-1) ; + + exec_signal_terminate(); + #ifdef __linux #ifdef __GNUC__ #if __GNUC__ >= 4 && __GNUC_MINOR__ >= 2 @@ -128,19 +115,17 @@ void * Trick::VariableServerThread::thread_body() { throw; #else message_publish(MSG_ERROR, "\nVARIABLE SERVER caught unknown exception\n" ) ; - exit(-1) ; + exec_signal_terminate(); #endif #endif #endif } - if (debug >= 3) { - message_publish(MSG_DEBUG, "%p tag=<%s> var_server receive loop exiting\n", connection, connection->getClientTag().c_str()); + if (_debug >= 3) { + message_publish(MSG_DEBUG, "%p tag=<%s> var_server receive loop exiting\n", _connection, _connection->getClientTag().c_str()); } thread_shutdown(exit_var_thread, this); - - return NULL ; - + // No return from this. } diff --git a/trick_source/sim_services/VariableServer/VariableServerSession_commands.cpp b/trick_source/sim_services/VariableServer/VariableServerSession_commands.cpp index bd399503..7d7d2e6f 100644 --- a/trick_source/sim_services/VariableServer/VariableServerSession_commands.cpp +++ b/trick_source/sim_services/VariableServer/VariableServerSession_commands.cpp @@ -7,7 +7,6 @@ #include "trick/VariableServerSession.hh" #include "trick/variable_server_message_types.h" #include "trick/memorymanager_c_intf.h" -// #include "trick/tc_proto.h" #include "trick/exec_proto.h" #include "trick/command_line_protos.h" #include "trick/message_proto.h" @@ -21,12 +20,12 @@ int Trick::VariableServerSession::var_add(std::string in_name) { VariableReference * new_var; if (in_name == "time") { - new_var = new VariableReference(in_name, &time); + new_var = new VariableReference(in_name, &_time); } else { new_var = new VariableReference(in_name); } - session_variables.push_back(new_var) ; + _session_variables.push_back(new_var) ; return(0) ; } @@ -60,7 +59,7 @@ int Trick::VariableServerSession::var_send_once(std::string in_name, int num_var for (auto& varName : var_names) { VariableReference * new_var; if (varName == "time") { - new_var = new VariableReference(varName, &time); + new_var = new VariableReference(varName, &_time); } else { new_var = new VariableReference(varName); } @@ -75,11 +74,11 @@ int Trick::VariableServerSession::var_send_once(std::string in_name, int num_var int Trick::VariableServerSession::var_remove(std::string in_name) { - for (unsigned int ii = 0 ; ii < session_variables.size() ; ii++ ) { - std::string var_name = session_variables[ii]->getName(); + for (unsigned int ii = 0 ; ii < _session_variables.size() ; ii++ ) { + std::string var_name = _session_variables[ii]->getName(); if ( ! var_name.compare(in_name) ) { - delete session_variables[ii]; - session_variables.erase(session_variables.begin() + ii) ; + delete _session_variables[ii]; + _session_variables.erase(_session_variables.begin() + ii) ; break ; } } @@ -110,29 +109,29 @@ int Trick::VariableServerSession::var_exists(std::string in_name) { error = true; } - if (binary_data) { + if (_binary_data) { /* send binary 1 or 0 */ msg_type = VS_VAR_EXISTS ; memcpy(buf1, &msg_type , sizeof(msg_type)) ; buf1[4] = (error==false); - if (debug >= 2) { - // message_publish(MSG_DEBUG, "%p tag=<%s> var_server sending 1 binary byte\n", &connection, connection.client_tag); + if (_debug >= 2) { + // message_publish(MSG_DEBUG, "%p tag=<%s> var_server sending 1 binary byte\n", &_connection, _connection.client_tag); } - connection->write(buf1, 5); + _connection->write(buf1, 5); } else { /* send ascii "1" or "0" */ sprintf(buf1, "%d\t%d\n", VS_VAR_EXISTS, (error==false)); - if (debug >= 2) { - // message_publish(MSG_DEBUG, "%p tag=<%s> var_server sending:\n%s\n", &connection, connection.client_tag, buf1) ; + if (_debug >= 2) { + // message_publish(MSG_DEBUG, "%p tag=<%s> var_server sending:\n%s\n", &_connection, _connection.client_tag, buf1) ; } std::string write_string(buf1); if (write_string.length() != strlen(buf1)) { std::cout << "PROBLEM WITH STRING LENGTH: VAR_EXISTS ASCII" << std::endl; } - connection->write(write_string); + _connection->write(write_string); } return(0) ; @@ -140,9 +139,9 @@ int Trick::VariableServerSession::var_exists(std::string in_name) { int Trick::VariableServerSession::var_clear() { - while( !session_variables.empty() ) { - delete session_variables.back(); - session_variables.pop_back(); + while( !_session_variables.empty() ) { + delete _session_variables.back(); + _session_variables.pop_back(); } return(0) ; @@ -155,67 +154,59 @@ int Trick::VariableServerSession::var_send() { } int Trick::VariableServerSession::var_cycle(double in_rate) { - update_rate = in_rate ; - cycle_tics = (long long)(update_rate * exec_get_time_tic_value()) ; + _update_rate = in_rate ; + _cycle_tics = (long long)(_update_rate * exec_get_time_tic_value()) ; return(0) ; } -bool Trick::VariableServerSession::get_pause() { - return pause_cmd ; -} - -void Trick::VariableServerSession::set_pause( bool on_off) { - pause_cmd = on_off ; -} - int Trick::VariableServerSession::var_exit() { - exit_cmd = true ; + _exit_cmd = true ; return(0) ; } int Trick::VariableServerSession::var_validate_address(bool on_off) { - validate_address = on_off ; + _validate_address = on_off ; return(0) ; } int Trick::VariableServerSession::var_debug(int level) { - debug = level ; + _debug = level ; return(0) ; } int Trick::VariableServerSession::var_ascii() { - binary_data = 0 ; + _binary_data = 0 ; return(0) ; } int Trick::VariableServerSession::var_binary() { - binary_data = 1 ; + _binary_data = 1 ; return(0) ; } int Trick::VariableServerSession::var_binary_nonames() { - binary_data = 1 ; - binary_data_nonames = 1 ; + _binary_data = 1 ; + _binary_data_nonames = 1 ; return(0) ; } int Trick::VariableServerSession::var_set_copy_mode(int mode) { if ( mode >= VS_COPY_ASYNC and mode <= VS_COPY_TOP_OF_FRAME ) { - copy_mode = (VS_COPY_MODE)mode ; - if ( copy_mode == VS_COPY_SCHEDULED ) { + _copy_mode = (VS_COPY_MODE)mode ; + if ( _copy_mode == VS_COPY_SCHEDULED ) { long long sim_time_tics ; sim_time_tics = exec_get_time_tics() ; // round the next call time to a multiple of the cycle - sim_time_tics -= sim_time_tics % cycle_tics ; - next_tics = sim_time_tics + cycle_tics ; + sim_time_tics -= sim_time_tics % _cycle_tics ; + _next_tics = sim_time_tics + _cycle_tics ; sim_time_tics = exec_get_freeze_time_tics() ; // round the next call time to a multiple of the cycle - sim_time_tics -= sim_time_tics % cycle_tics ; - freeze_next_tics = sim_time_tics + cycle_tics ; + sim_time_tics -= sim_time_tics % _cycle_tics ; + _freeze_next_tics = sim_time_tics + _cycle_tics ; } else { - next_tics = TRICK_MAX_LONG_LONG ; + _next_tics = TRICK_MAX_LONG_LONG ; } return 0 ; } @@ -224,7 +215,7 @@ int Trick::VariableServerSession::var_set_copy_mode(int mode) { int Trick::VariableServerSession::var_set_write_mode(int mode) { if ( mode >= VS_WRITE_ASYNC and mode <= VS_WRITE_WHEN_COPIED ) { - write_mode = (VS_WRITE_MODE)mode ; + _write_mode = (VS_WRITE_MODE)mode ; return 0 ; } return -1 ; @@ -252,46 +243,46 @@ int Trick::VariableServerSession::var_sync(int mode) { } int Trick::VariableServerSession::var_set_frame_multiple(unsigned int mult) { - frame_multiple = mult ; + _frame_multiple = mult ; return 0 ; } int Trick::VariableServerSession::var_set_frame_offset(unsigned int offset) { - frame_offset = offset ; + _frame_offset = offset ; return 0 ; } int Trick::VariableServerSession::var_set_freeze_frame_multiple(unsigned int mult) { - freeze_frame_multiple = mult ; + _freeze_frame_multiple = mult ; return 0 ; } int Trick::VariableServerSession::var_set_freeze_frame_offset(unsigned int offset) { - freeze_frame_offset = offset ; + _freeze_frame_offset = offset ; return 0 ; } int Trick::VariableServerSession::var_byteswap(bool on_off) { - byteswap = on_off ; + _byteswap = on_off ; return(0) ; } bool Trick::VariableServerSession::get_send_stdio() { - return send_stdio ; + return _send_stdio ; } int Trick::VariableServerSession::set_send_stdio(bool on_off) { - send_stdio = on_off ; + _send_stdio = on_off ; return(0) ; } int Trick::VariableServerSession::send_list_size() { unsigned int msg_type = VS_LIST_SIZE; - int var_count = session_variables.size(); + int var_count = _session_variables.size(); // send number of variables - if (binary_data) { + if (_binary_data) { // send in the binary message header format: // char buf1[12] ; @@ -302,20 +293,20 @@ int Trick::VariableServerSession::send_list_size() { memset(&(buf1[4]), 0, sizeof(int)); // message size = 0 memcpy(&(buf1[8]), &var_count, sizeof(var_count)); - if (debug >= 2) { - message_publish(MSG_DEBUG, "%p tag=<%s> var_server sending %d event variables\n", connection, connection->getClientTag().c_str(), var_count); + if (_debug >= 2) { + message_publish(MSG_DEBUG, "%p tag=<%s> var_server sending %d event variables\n", _connection, _connection->getClientTag().c_str(), var_count); } - connection->write(buf1, sizeof (buf1)); + _connection->write(buf1, sizeof (buf1)); } else { std::stringstream write_string; write_string << VS_LIST_SIZE << "\t" << var_count << "\n"; // ascii - if (debug >= 2) { - message_publish(MSG_DEBUG, "%p tag=<%s> var_server sending number of event variables:\n%s\n", connection, connection->getClientTag().c_str(), write_string.str().c_str()) ; + if (_debug >= 2) { + message_publish(MSG_DEBUG, "%p tag=<%s> var_server sending number of event variables:\n%s\n", _connection, _connection->getClientTag().c_str(), write_string.str().c_str()) ; } - connection->write(write_string.str()); + _connection->write(write_string.str()); } return 0 ; @@ -330,15 +321,15 @@ int Trick::VariableServerSession::transmit_file(std::string sie_file) { char buffer[packet_size+1] ; int ret ; - if (debug >= 2) { - message_publish(MSG_DEBUG,"%p tag=<%s> var_server opening %s.\n", connection, connection->getClientTag().c_str(), sie_file.c_str()) ; + if (_debug >= 2) { + message_publish(MSG_DEBUG,"%p tag=<%s> var_server opening %s.\n", _connection, _connection->getClientTag().c_str(), sie_file.c_str()) ; } if ((fp = fopen(sie_file.c_str() , "r")) == NULL ) { message_publish(MSG_ERROR,"Variable Server Error: Cannot open %s.\n", sie_file.c_str()) ; sprintf(buffer, "%d\t-1\n", VS_SIE_RESOURCE) ; std::string message(buffer); - connection->write(message); + _connection->write(message); return(-1) ; } @@ -347,11 +338,11 @@ int Trick::VariableServerSession::transmit_file(std::string sie_file) { sprintf(buffer, "%d\t%u\n\0" , VS_SIE_RESOURCE, file_size) ; std::string message(buffer); - connection->write(message); + _connection->write(message); rewind(fp) ; // Switch to blocking writes since this could be a large transfer. - if (connection->setBlockMode(true)) { + if (_connection->setBlockMode(true)) { message_publish(MSG_DEBUG,"Variable Server Error: Failed to set socket to blocking mode.\n"); } @@ -359,7 +350,7 @@ int Trick::VariableServerSession::transmit_file(std::string sie_file) { bytes_read = fread(buffer , 1 , packet_size , fp) ; message = std::string(buffer); message.resize(bytes_read); - ret = connection->write(message); + ret = _connection->write(message); if (ret != (int)bytes_read) { message_publish(MSG_ERROR,"Variable Server Error: Failed to send SIE file. Bytes read: %d Bytes sent: %d\n", bytes_read, ret) ; return(-1); @@ -368,7 +359,7 @@ int Trick::VariableServerSession::transmit_file(std::string sie_file) { } // Switch back to non-blocking writes. - if (connection->setBlockMode(false)) { + if (_connection->setBlockMode(false)) { message_publish(MSG_DEBUG,"Variable Server Error: Failed to set socket to non-blocking mode.\n"); return(-1); } diff --git a/trick_source/sim_services/VariableServer/VariableServerSession_copy_and_write_modes.cpp b/trick_source/sim_services/VariableServer/VariableServerSession_copy_and_write_modes.cpp new file mode 100644 index 00000000..15a74656 --- /dev/null +++ b/trick_source/sim_services/VariableServer/VariableServerSession_copy_and_write_modes.cpp @@ -0,0 +1,119 @@ + +#include +#include + +#include "trick/VariableServerSession.hh" +#include "trick/variable_server_sync_types.h" +#include "trick/realtimesync_proto.h" + +// These methods should be called from approprate jobs or from the VST + +int Trick::VariableServerSession::copy_and_write_freeze(long long curr_freeze_frame) { + int ret = 0 ; + + if (!get_enabled()) + return ret; + + if (get_copy_mode() == VS_COPY_TOP_OF_FRAME) { + long long temp_frame = curr_freeze_frame % get_freeze_frame_multiple() ; + if ( temp_frame == get_freeze_frame_offset() ) { + copy_sim_data() ; + if ( !get_pause() and get_write_mode() == VS_WRITE_WHEN_COPIED and is_real_time()) { + ret = write_data() ; + if ( ret < 0 ) { + set_exit_cmd(); + } + } + } + } + + return ret ; +} + +int Trick::VariableServerSession::copy_and_write_freeze_scheduled(long long curr_tics) { + int ret = 0 ; + + if (!get_enabled()) + return ret; + + if (get_copy_mode() == VS_COPY_SCHEDULED) { + if ( get_freeze_next_tics() <= curr_tics ) { + copy_sim_data() ; + if ( !get_pause() && get_write_mode() == VS_WRITE_WHEN_COPIED && is_real_time()) { + ret = write_data() ; + if ( ret < 0 ) { + set_exit_cmd(); + } + } + set_freeze_next_tics(curr_tics + get_cycle_tics()) ; + } + } + return ret ; +} + +int Trick::VariableServerSession::copy_and_write_scheduled(long long curr_tics) { + int ret = 0 ; + + if (!get_enabled()) + return ret; + + if (get_copy_mode() == VS_COPY_SCHEDULED) { + if ( get_next_tics() <= curr_tics ) { + copy_sim_data() ; + if ( !get_pause() && get_write_mode() == VS_WRITE_WHEN_COPIED && is_real_time()) { + ret = write_data() ; + if ( ret < 0 ) { + set_exit_cmd(); + } + } + set_next_tics(curr_tics + get_cycle_tics()) ; + } + } + return ret ; +} + +int Trick::VariableServerSession::copy_and_write_top(long long curr_frame) { + int ret = 0 ; + + if (!get_enabled()) + return ret; + + if (get_copy_mode() == VS_COPY_TOP_OF_FRAME) { + long long temp_frame = curr_frame % get_frame_multiple() ; + if ( temp_frame == get_frame_offset() ) { + copy_sim_data() ; + if ( !get_pause() && get_write_mode() == VS_WRITE_WHEN_COPIED && is_real_time()) { + ret = write_data() ; + if ( ret < 0 ) { + set_exit_cmd(); + } + } + } + } + return ret ; +} + +int Trick::VariableServerSession::copy_and_write_async() { + int ret = 0; + + if (!get_enabled()) + return ret; + + if (get_copy_mode() == VS_COPY_ASYNC ) { + copy_sim_data() ; + } + + // Write data out to connection if async mode or non-realtime, and not paused + bool should_write_async = (get_write_mode() == VS_WRITE_ASYNC) || + ( get_copy_mode() == VS_COPY_ASYNC && get_write_mode() == VS_WRITE_WHEN_COPIED)|| + (! is_real_time()); + + if ( !get_pause() && should_write_async) { + ret = write_data() ; + if ( ret < 0 ) { + set_exit_cmd(); + } + } + + return ret; +} diff --git a/trick_source/sim_services/VariableServer/VariableServerSession_copy_data.cpp b/trick_source/sim_services/VariableServer/VariableServerSession_copy_data.cpp deleted file mode 100644 index 2b6325fc..00000000 --- a/trick_source/sim_services/VariableServer/VariableServerSession_copy_data.cpp +++ /dev/null @@ -1,90 +0,0 @@ - -#include -#include - -#include "trick/VariableServerSession.hh" -#include "trick/variable_server_sync_types.h" -#include "trick/exec_proto.h" -#include "trick/realtimesync_proto.h" - -// These methods should be called from main thread jobs - -int Trick::VariableServerSession::copy_data_freeze() { - - int ret = 0 ; - long long curr_frame = exec_get_freeze_frame_count() ; - long long temp_frame ; - - if ( enabled and copy_mode == VS_COPY_TOP_OF_FRAME) { - temp_frame = curr_frame % freeze_frame_multiple ; - if ( temp_frame == freeze_frame_offset ) { - copy_sim_data() ; - if ( !pause_cmd and write_mode == VS_WRITE_WHEN_COPIED and is_real_time()) { - ret = write_data() ; - if ( ret < 0 ) { - exit_cmd = true ; - } - } - } - } - return ret ; -} - -int Trick::VariableServerSession::copy_data_freeze_scheduled(long long curr_tics) { - - int ret = 0 ; - - if ( enabled and copy_mode == VS_COPY_SCHEDULED) { - if ( freeze_next_tics <= curr_tics ) { - copy_sim_data() ; - if ( !pause_cmd and write_mode == VS_WRITE_WHEN_COPIED and is_real_time()) { - ret = write_data() ; - if ( ret < 0 ) { - exit_cmd = true ; - } - } - freeze_next_tics = curr_tics + cycle_tics ; - } - } - return ret ; -} - -int Trick::VariableServerSession::copy_data_scheduled(long long curr_tics) { - - int ret = 0 ; - - if ( enabled and copy_mode == VS_COPY_SCHEDULED) { - if ( next_tics <= curr_tics ) { - copy_sim_data() ; - if ( !pause_cmd and write_mode == VS_WRITE_WHEN_COPIED and is_real_time()) { - ret = write_data() ; - if ( ret < 0 ) { - exit_cmd = true ; - } - } - next_tics = curr_tics + cycle_tics ; - } - } - return ret ; -} - -int Trick::VariableServerSession::copy_data_top() { - - int ret = 0 ; - long long curr_frame = exec_get_frame_count() ; - long long temp_frame ; - - if ( enabled and copy_mode == VS_COPY_TOP_OF_FRAME) { - temp_frame = curr_frame % frame_multiple ; - if ( temp_frame == frame_offset ) { - copy_sim_data() ; - if ( !pause_cmd and write_mode == VS_WRITE_WHEN_COPIED and is_real_time()) { - ret = write_data() ; - if ( ret < 0 ) { - exit_cmd = true ; - } - } - } - } - return ret ; -} diff --git a/trick_source/sim_services/VariableServer/VariableServerSession_copy_sim_data.cpp b/trick_source/sim_services/VariableServer/VariableServerSession_copy_sim_data.cpp index 2598f39f..50febc3f 100644 --- a/trick_source/sim_services/VariableServer/VariableServerSession_copy_sim_data.cpp +++ b/trick_source/sim_services/VariableServer/VariableServerSession_copy_sim_data.cpp @@ -10,7 +10,7 @@ // These actually do the copying int Trick::VariableServerSession::copy_sim_data() { - return copy_sim_data(session_variables, true); + return copy_sim_data(_session_variables, true); } int Trick::VariableServerSession::copy_sim_data(std::vector& given_vars, bool cyclical) { @@ -19,16 +19,16 @@ int Trick::VariableServerSession::copy_sim_data(std::vector return 0; } - if ( pthread_mutex_trylock(©_mutex) == 0 ) { + if ( pthread_mutex_trylock(&_copy_mutex) == 0 ) { // Get the simulation time we start this copy - time = (double)exec_get_time_tics() / exec_get_time_tic_value() ; + _time = (double)exec_get_time_tics() / exec_get_time_tic_value() ; for (auto curr_var : given_vars ) { curr_var->stageValue(); } - pthread_mutex_unlock(©_mutex) ; + pthread_mutex_unlock(&_copy_mutex) ; } return 0; diff --git a/trick_source/sim_services/VariableServer/VariableServerSession_freeze_init.cpp b/trick_source/sim_services/VariableServer/VariableServerSession_freeze_init.cpp index 03baeba2..0f16323b 100644 --- a/trick_source/sim_services/VariableServer/VariableServerSession_freeze_init.cpp +++ b/trick_source/sim_services/VariableServer/VariableServerSession_freeze_init.cpp @@ -6,10 +6,10 @@ #include "trick/TrickConstant.hh" int Trick::VariableServerSession::freeze_init() { - if ( enabled && copy_mode == VS_COPY_SCHEDULED) { - freeze_next_tics = cycle_tics ; + if ( _enabled && _copy_mode == VS_COPY_SCHEDULED) { + _freeze_next_tics = _cycle_tics ; } else { - freeze_next_tics = TRICK_MAX_LONG_LONG ; + _freeze_next_tics = TRICK_MAX_LONG_LONG ; } return 0 ; } diff --git a/trick_source/sim_services/VariableServer/VariableServerSession_write_data.cpp b/trick_source/sim_services/VariableServer/VariableServerSession_write_data.cpp index 33cda9ab..2ae2fa03 100644 --- a/trick_source/sim_services/VariableServer/VariableServerSession_write_data.cpp +++ b/trick_source/sim_services/VariableServer/VariableServerSession_write_data.cpp @@ -15,11 +15,6 @@ PROGRAMMERS: (((Alex Lin) (NASA) (8/06) (--))) #include "trick/message_proto.h" #include "trick/message_type.h" - -extern "C" { - void *trick_bswap_buffer(void *out, void *in, ATTRIBUTES * attr, int tofrom) ; -} - #define MAX_MSG_LEN 8192 @@ -37,9 +32,9 @@ int Trick::VariableServerSession::write_binary_data(const std::vectorgetName()); + total_var_size += var->getName().size(); } total_var_size += type_size; @@ -70,7 +65,7 @@ int Trick::VariableServerSession::write_binary_data(const std::vectorwriteNameBinary(stream, byteswap); + if (!_binary_data_nonames) { + var->writeNameBinary(stream, _byteswap); } - var->writeTypeBinary(stream, byteswap); - var->writeSizeBinary(stream, byteswap); - var->writeValueBinary(stream, byteswap); + var->writeTypeBinary(stream, _byteswap); + var->writeSizeBinary(stream, _byteswap); + var->writeValueBinary(stream, _byteswap); } var_index += curr_message_num_vars; + // Send it out! char write_buf[MAX_MSG_LEN]; stream.read(write_buf, curr_message_size); - connection->write(write_buf, curr_message_size); + _connection->write(write_buf, curr_message_size); } return 0; @@ -118,6 +114,7 @@ int Trick::VariableServerSession::write_ascii_data(const std::vectorwrite(message); + int result = _connection->write(message); if (result < 0) return result; + // Clear out the message stream message_stream.str(""); message_size = 0; @@ -143,12 +141,12 @@ int Trick::VariableServerSession::write_ascii_data(const std::vectorwrite(message); + int result = _connection->write(message); return result; } int Trick::VariableServerSession::write_data() { - return write_data(session_variables, VS_VAR_LIST); + return write_data(_session_variables, VS_VAR_LIST); } int Trick::VariableServerSession::write_data(std::vector& given_vars, VS_MESSAGE_TYPE message_type) { @@ -159,12 +157,12 @@ int Trick::VariableServerSession::write_data(std::vector& g int result = 0; - if ( pthread_mutex_trylock(©_mutex) == 0 ) { + if ( pthread_mutex_trylock(&_copy_mutex) == 0 ) { // Check that all of the variables are staged for (VariableReference * variable : given_vars ) { if (!variable->isStaged()) { - pthread_mutex_unlock(©_mutex) ; - return 1; + pthread_mutex_unlock(&_copy_mutex) ; + return 0; } } @@ -173,10 +171,10 @@ int Trick::VariableServerSession::write_data(std::vector& g variable->prepareForWrite(); } - pthread_mutex_unlock(©_mutex) ; + pthread_mutex_unlock(&_copy_mutex) ; // Send out in correct format - if (binary_data) { + if (_binary_data) { result = write_binary_data(given_vars, message_type ); } else { // ascii mode diff --git a/trick_source/sim_services/VariableServer/VariableServerSession_write_stdio.cpp b/trick_source/sim_services/VariableServer/VariableServerSession_write_stdio.cpp index 7732867e..f95e77bf 100644 --- a/trick_source/sim_services/VariableServer/VariableServerSession_write_stdio.cpp +++ b/trick_source/sim_services/VariableServer/VariableServerSession_write_stdio.cpp @@ -11,7 +11,7 @@ int Trick::VariableServerSession::write_stdio(int stream, std::string text) { outstream << VS_STDIO << " " << stream << " " << (int)text.length() << "\n"; outstream << text; - connection->write(outstream.str()); + _connection->write(outstream.str()); return 0 ; } diff --git a/trick_source/sim_services/VariableServer/VariableServerThread.cpp b/trick_source/sim_services/VariableServer/VariableServerThread.cpp deleted file mode 100644 index 659df23a..00000000 --- a/trick_source/sim_services/VariableServer/VariableServerThread.cpp +++ /dev/null @@ -1,159 +0,0 @@ - -#include -#include -#include "trick/VariableServerThread.hh" -#include "trick/exec_proto.h" -#include "trick/message_proto.h" -#include "trick/message_type.h" -#include "trick/TrickConstant.hh" -#include "trick/UDPConnection.hh" -#include "trick/TCPConnection.hh" - - -Trick::VariableServer * Trick::VariableServerThread::vs = NULL ; - -static int instance_num = 0; - -Trick::VariableServerThread::VariableServerThread() : - Trick::SysThread(std::string("VarServer" + std::to_string(instance_num++))) , debug(0), session(NULL), connection(NULL) { - - connection_status = CONNECTION_PENDING ; - - - pthread_mutex_init(&connection_status_mutex, NULL); - pthread_cond_init(&connection_status_cv, NULL); - - pthread_mutex_init(&restart_pause, NULL); - - cancellable = false; -} - -Trick::VariableServerThread::~VariableServerThread() {} - -std::ostream& Trick::operator<< (std::ostream& s, Trick::VariableServerThread& vst) { - // Write a JSON representation of a Trick::VariableServerThread to an ostream. - struct sockaddr_in otherside; - socklen_t len = (socklen_t)sizeof(otherside); - - s << " \"connection\":{\n"; - s << " \"client_tag\":\"" << vst.connection->getClientTag() << "\",\n"; - - // int err = getpeername(vst.connection->get_socket(), (struct sockaddr*)&otherside, &len); - - // if (err == 0) { - // s << " \"client_IP_address\":\"" << inet_ntoa(otherside.sin_addr) << "\",\n"; - // s << " \"client_port\":\"" << ntohs(otherside.sin_port) << "\",\n"; - // } else { - // s << " \"client_IP_address\":\"unknown\",\n"; - // s << " \"client_port\":\"unknown\","; - // } - - pthread_mutex_lock(&vst.connection_status_mutex); - if (vst.connection_status == CONNECTION_SUCCESS) { - s << *(vst.session); - } - pthread_mutex_unlock(&vst.connection_status_mutex); - - s << " }" << std::endl; - return s; -} - -void Trick::VariableServerThread::set_vs_ptr(Trick::VariableServer * in_vs) { - vs = in_vs ; -} - -Trick::VariableServer * Trick::VariableServerThread::get_vs() { - return vs ; -} - -void Trick::VariableServerThread::set_client_tag(std::string tag) { - connection->setClientTag(tag); -} - -int Trick::VariableServerThread::open_udp_socket(const std::string& hostname, int port) { - UDPConnection * udp_conn = new UDPConnection(); - int status = udp_conn->initialize(hostname, port); - - connection = udp_conn; - - if (status == 0) { - message_publish(MSG_INFO, "Created UDP variable server %s: %d\n", udp_conn->getHostname().c_str(), udp_conn->getPort()); - } - - return status; -} - -int Trick::VariableServerThread::open_tcp_connection(ClientListener * listener) { - connection = listener->setUpNewConnection(); - - return 0; -} - -Trick::ConnectionStatus Trick::VariableServerThread::wait_for_accept() { - - pthread_mutex_lock(&connection_status_mutex); - while ( connection_status == CONNECTION_PENDING ) { - pthread_cond_wait(&connection_status_cv, &connection_status_mutex); - } - pthread_mutex_unlock(&connection_status_mutex); - - return connection_status; -} - -// This should maybe go somewhere completely different -// Leaving it in thread for now -// Gets called from the main thread as a job -void Trick::VariableServerThread::preload_checkpoint() { - - // Stop variable server processing at the top of the processing loop. - pthread_mutex_lock(&restart_pause); - - // Make sure that the session has been initialized - pthread_mutex_lock(&connection_status_mutex); - if (connection_status == CONNECTION_SUCCESS) { - - // Let the thread complete any data copying it has to do - // and then suspend data copying until the checkpoint is reloaded. - pthread_mutex_lock(&(session->copy_mutex)); - - // Save the pause state of this thread. - saved_pause_cmd = session->get_pause(); - - // Disallow data writing. - session->set_pause(true); - - // Temporarily "disconnect" the variable references from Trick Managed Memory - // by tagging each as a "bad reference". - session->disconnect_references(); - - - // Allow data copying to continue. - pthread_mutex_unlock(&(session->copy_mutex)); - - } - pthread_mutex_unlock(&connection_status_mutex); -} - -// Gets called from the main thread as a job -void Trick::VariableServerThread::restart() { - // Set the pause state of this thread back to its "pre-checkpoint reload" state. - connection->restart(); - - pthread_mutex_lock(&connection_status_mutex); - if (connection_status == CONNECTION_SUCCESS) { - session->set_pause(saved_pause_cmd) ; - } - pthread_mutex_unlock(&connection_status_mutex); - - // Restart the variable server processing. - pthread_mutex_unlock(&restart_pause); -} - -void Trick::VariableServerThread::cleanup() { - connection->disconnect(); - - if (session != NULL) - delete session; -} - - diff --git a/trick_source/sim_services/VariableServer/VariableServer_copy_and_write_freeze.cpp b/trick_source/sim_services/VariableServer/VariableServer_copy_and_write_freeze.cpp new file mode 100644 index 00000000..81a479d1 --- /dev/null +++ b/trick_source/sim_services/VariableServer/VariableServer_copy_and_write_freeze.cpp @@ -0,0 +1,13 @@ +#include "trick/VariableServer.hh" +#include "trick/exec_proto.h" + +int Trick::VariableServer::copy_and_write_freeze() { + + pthread_mutex_lock(&map_mutex) ; + for ( auto it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { + (*it).second->copy_and_write_freeze(exec_get_freeze_frame_count()) ; + } + pthread_mutex_unlock(&map_mutex) ; + + return 0 ; +} diff --git a/trick_source/sim_services/VariableServer/VariableServer_copy_data_freeze_scheduled.cpp b/trick_source/sim_services/VariableServer/VariableServer_copy_and_write_freeze_scheduled.cpp similarity index 52% rename from trick_source/sim_services/VariableServer/VariableServer_copy_data_freeze_scheduled.cpp rename to trick_source/sim_services/VariableServer/VariableServer_copy_and_write_freeze_scheduled.cpp index cd6ecf7d..511afbd2 100644 --- a/trick_source/sim_services/VariableServer/VariableServer_copy_data_freeze_scheduled.cpp +++ b/trick_source/sim_services/VariableServer/VariableServer_copy_and_write_freeze_scheduled.cpp @@ -5,17 +5,14 @@ #include "trick/VariableServer.hh" #include "trick/TrickConstant.hh" -int Trick::VariableServer::copy_data_freeze_scheduled() { +int Trick::VariableServer::copy_and_write_freeze_scheduled() { - long long next_call_tics ; - std::map < pthread_t , VariableServerSession * >::iterator it ; - - next_call_tics = TRICK_MAX_LONG_LONG ; + long long next_call_tics = TRICK_MAX_LONG_LONG ; pthread_mutex_lock(&map_mutex) ; - for ( it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { + for ( auto it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { VariableServerSession * session = (*it).second ; - session->copy_data_freeze_scheduled(copy_data_freeze_job->next_tics) ; + session->copy_and_write_freeze_scheduled(copy_and_write_freeze_job->next_tics) ; if ( session->get_freeze_next_tics() < next_call_tics ) { next_call_tics = session->get_freeze_next_tics() ; } @@ -23,8 +20,8 @@ int Trick::VariableServer::copy_data_freeze_scheduled() { pthread_mutex_unlock(&map_mutex) ; //reschedule the current job. TODO: a call needs to be created to do this the OO way - if ( copy_data_freeze_job != NULL ) { - copy_data_freeze_job->next_tics = next_call_tics ; + if ( copy_and_write_freeze_job != NULL ) { + copy_and_write_freeze_job->next_tics = next_call_tics ; } return(0) ; diff --git a/trick_source/sim_services/VariableServer/VariableServer_copy_data_scheduled.cpp b/trick_source/sim_services/VariableServer/VariableServer_copy_and_write_scheduled.cpp similarity index 57% rename from trick_source/sim_services/VariableServer/VariableServer_copy_data_scheduled.cpp rename to trick_source/sim_services/VariableServer/VariableServer_copy_and_write_scheduled.cpp index 4ba077ea..9ef7fde2 100644 --- a/trick_source/sim_services/VariableServer/VariableServer_copy_data_scheduled.cpp +++ b/trick_source/sim_services/VariableServer/VariableServer_copy_and_write_scheduled.cpp @@ -5,17 +5,14 @@ #include "trick/VariableServer.hh" #include "trick/TrickConstant.hh" -int Trick::VariableServer::copy_data_scheduled() { +int Trick::VariableServer::copy_and_write_scheduled() { - long long next_call_tics ; - std::map < pthread_t , VariableServerSession * >::iterator it ; - - next_call_tics = TRICK_MAX_LONG_LONG ; + long long next_call_tics = TRICK_MAX_LONG_LONG; pthread_mutex_lock(&map_mutex) ; - for ( it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { - auto session = (*it).second ; - session->copy_data_scheduled(copy_data_job->next_tics) ; + for ( auto it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { + VariableServerSession * session = (*it).second ; + session->copy_and_write_scheduled(copy_data_job->next_tics) ; if ( session->get_next_tics() < next_call_tics ) { next_call_tics = session->get_next_tics() ; } diff --git a/trick_source/sim_services/VariableServer/VariableServer_copy_and_write_top.cpp b/trick_source/sim_services/VariableServer/VariableServer_copy_and_write_top.cpp new file mode 100644 index 00000000..3118098d --- /dev/null +++ b/trick_source/sim_services/VariableServer/VariableServer_copy_and_write_top.cpp @@ -0,0 +1,13 @@ +#include "trick/exec_proto.h" +#include "trick/VariableServer.hh" + +int Trick::VariableServer::copy_and_write_top() { + + pthread_mutex_lock(&map_mutex) ; + for ( auto it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { + (*it).second->copy_and_write_top(exec_get_frame_count()) ; + } + pthread_mutex_unlock(&map_mutex) ; + + return 0 ; +} diff --git a/trick_source/sim_services/VariableServer/VariableServer_copy_data_freeze.cpp b/trick_source/sim_services/VariableServer/VariableServer_copy_data_freeze.cpp deleted file mode 100644 index 62b4bcdf..00000000 --- a/trick_source/sim_services/VariableServer/VariableServer_copy_data_freeze.cpp +++ /dev/null @@ -1,18 +0,0 @@ - -#include -#include - -#include "trick/VariableServer.hh" - -int Trick::VariableServer::copy_data_freeze() { - - std::map < pthread_t , VariableServerSession * >::iterator it ; - - pthread_mutex_lock(&map_mutex) ; - for ( it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { - (*it).second->copy_data_freeze() ; - } - pthread_mutex_unlock(&map_mutex) ; - - return 0 ; -} diff --git a/trick_source/sim_services/VariableServer/VariableServer_copy_data_top.cpp b/trick_source/sim_services/VariableServer/VariableServer_copy_data_top.cpp deleted file mode 100644 index 3b4dad61..00000000 --- a/trick_source/sim_services/VariableServer/VariableServer_copy_data_top.cpp +++ /dev/null @@ -1,18 +0,0 @@ - -#include -#include - -#include "trick/VariableServer.hh" - -int Trick::VariableServer::copy_data_top() { - - std::map < pthread_t , VariableServerSession * >::iterator it ; - - pthread_mutex_lock(&map_mutex) ; - for ( it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { - (*it).second->copy_data_top() ; - } - pthread_mutex_unlock(&map_mutex) ; - - return 0 ; -} diff --git a/trick_source/sim_services/VariableServer/VariableServer_freeze_init.cpp b/trick_source/sim_services/VariableServer/VariableServer_freeze_init.cpp index 8f7bddce..c7a81c04 100644 --- a/trick_source/sim_services/VariableServer/VariableServer_freeze_init.cpp +++ b/trick_source/sim_services/VariableServer/VariableServer_freeze_init.cpp @@ -9,15 +9,11 @@ int Trick::VariableServer::freeze_init() { - long long next_call_tics ; - VariableServerSession * session ; - std::map < pthread_t , VariableServerSession * >::iterator it ; - - next_call_tics = TRICK_MAX_LONG_LONG ; + long long next_call_tics = TRICK_MAX_LONG_LONG ; pthread_mutex_lock(&map_mutex) ; - for ( it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { - session = (*it).second ; + for ( auto it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { + VariableServerSession * session = (*it).second ; session->freeze_init() ; if ( session->get_freeze_next_tics() < next_call_tics ) { next_call_tics = session->get_freeze_next_tics() ; @@ -26,8 +22,8 @@ int Trick::VariableServer::freeze_init() { pthread_mutex_unlock(&map_mutex) ; //reschedule the current job. TODO: a call needs to be created to do this the OO way - if ( copy_data_freeze_job != NULL ) { - copy_data_freeze_job->next_tics = next_call_tics ; + if ( copy_and_write_freeze_job != NULL ) { + copy_and_write_freeze_job->next_tics = next_call_tics ; } return(0) ; diff --git a/trick_source/sim_services/VariableServer/VariableServer_get_next_freeze_call_time.cpp b/trick_source/sim_services/VariableServer/VariableServer_get_next_freeze_call_time.cpp index 12c33189..f001c76f 100644 --- a/trick_source/sim_services/VariableServer/VariableServer_get_next_freeze_call_time.cpp +++ b/trick_source/sim_services/VariableServer/VariableServer_get_next_freeze_call_time.cpp @@ -6,23 +6,18 @@ int Trick::VariableServer::get_next_freeze_call_time() { - std::map < pthread_t , VariableServerSession * >::iterator it ; - VariableServerSession * session ; - - long long next_call_tics ; - - next_call_tics = TRICK_MAX_LONG_LONG ; + long long next_call_tics = TRICK_MAX_LONG_LONG; pthread_mutex_lock(&map_mutex) ; - for ( it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { - session = (*it).second ; + for ( auto it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { + VariableServerSession * session = (*it).second ; if ( session->get_freeze_next_tics() < next_call_tics ) { next_call_tics = session->get_freeze_next_tics() ; } } pthread_mutex_unlock(&map_mutex) ; - copy_data_freeze_job->next_tics = next_call_tics ; + copy_and_write_freeze_job->next_tics = next_call_tics ; return(0) ; } diff --git a/trick_source/sim_services/VariableServer/VariableServer_get_next_sync_call_time.cpp b/trick_source/sim_services/VariableServer/VariableServer_get_next_sync_call_time.cpp index e6d6d552..fd969d64 100644 --- a/trick_source/sim_services/VariableServer/VariableServer_get_next_sync_call_time.cpp +++ b/trick_source/sim_services/VariableServer/VariableServer_get_next_sync_call_time.cpp @@ -6,16 +6,11 @@ int Trick::VariableServer::get_next_sync_call_time() { - std::map < pthread_t , VariableServerSession * >::iterator it ; - VariableServerSession * session ; - - long long next_call_tics ; - - next_call_tics = TRICK_MAX_LONG_LONG ; + long long next_call_tics = TRICK_MAX_LONG_LONG ; pthread_mutex_lock(&map_mutex) ; - for ( it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { - session = (*it).second ; + for ( auto it = var_server_sessions.begin() ; it != var_server_sessions.end() ; it++ ) { + VariableServerSession * session = (*it).second ; if ( session->get_next_tics() < next_call_tics ) { next_call_tics = session->get_next_tics() ; } diff --git a/trick_source/sim_services/VariableServer/VariableServer_get_var_server_port.cpp b/trick_source/sim_services/VariableServer/VariableServer_get_var_server_port.cpp deleted file mode 100644 index 0e009dbd..00000000 --- a/trick_source/sim_services/VariableServer/VariableServer_get_var_server_port.cpp +++ /dev/null @@ -1,44 +0,0 @@ - -#include -#include "trick/VariableServer.hh" - - -int Trick::VariableServer::create_tcp_socket(const char * address, unsigned short in_port ) { - Trick::VariableServerListenThread * new_listen_thread = new Trick::VariableServerListenThread ; - new_listen_thread->create_tcp_socket(address, in_port) ; - new_listen_thread->copy_cpus(listen_thread.get_cpus()) ; - new_listen_thread->create_thread() ; - additional_listen_threads[new_listen_thread->get_pthread_id()] = new_listen_thread ; - - return 0 ; -} - -int Trick::VariableServer::create_udp_socket(const char * address, unsigned short in_port ) { - // UDP sockets are created without a listen thread - int ret ; - Trick::VariableServerThread * vst ; - vst = new Trick::VariableServerThread() ; - ret = vst->open_udp_socket(address, in_port) ; - if ( ret == 0 ) { - vst->copy_cpus(listen_thread.get_cpus()) ; - vst->create_thread() ; - } - //vst->var_debug(3) ; - - return 0 ; -} - -int Trick::VariableServer::create_multicast_socket(const char * mcast_address, const char * address, unsigned short in_port ) { - // int ret ; - // Trick::VariableServerThread * vst ; - // vst = new Trick::VariableServerThread(NULL) ; - // ret = vst->create_mcast_socket(mcast_address, address, in_port) ; - // if ( ret == 0 ) { - // vst->copy_cpus(listen_thread.get_cpus()) ; - // vst->create_thread() ; - // } - //vst->var_debug(3) ; - - return 0 ; -} - diff --git a/trick_source/sim_services/VariableServer/VariableServer_open_additional_servers.cpp b/trick_source/sim_services/VariableServer/VariableServer_open_additional_servers.cpp new file mode 100644 index 00000000..27445a51 --- /dev/null +++ b/trick_source/sim_services/VariableServer/VariableServer_open_additional_servers.cpp @@ -0,0 +1,88 @@ + +#include +#include "trick/VariableServer.hh" +#include "trick/message_proto.h" +#include "trick/message_type.h" + +#include "trick/TCPConnection.hh" +#include "trick/UDPConnection.hh" +#include "trick/TCPClientListener.hh" + +int Trick::VariableServer::create_tcp_socket(const char * address, unsigned short in_port ) { + // Open a VariableServerListenThread to manage this server + + TCPClientListener * listener = new TCPClientListener(); + int status = listener->initialize(address, in_port); + + if (status != 0) { + delete listener; + return 0; + } + + std::string set_address = listener->getHostname(); + int set_port = listener->getPort(); + + Trick::VariableServerListenThread * new_listen_thread = new Trick::VariableServerListenThread(listener) ; + + new_listen_thread->copy_cpus(listen_thread.get_cpus()) ; + new_listen_thread->create_thread() ; + additional_listen_threads[new_listen_thread->get_pthread_id()] = new_listen_thread ; + + message_publish(MSG_INFO, "Created TCP variable server %s: %d\n", set_address.c_str(), set_port); + + return 0 ; +} + +int Trick::VariableServer::create_udp_socket(const char * address, unsigned short in_port ) { + // UDP sockets are created without a listen thread, and represent only 1 session + // Create a VariableServerSessionThread to manage this session + + UDPConnection * udp_conn = new UDPConnection(); + int status = udp_conn->initialize(address, in_port); + if ( status != 0 ) { + delete udp_conn; + return 0; + } + + std::string set_address = udp_conn->getHostname(); + int set_port = udp_conn->getPort(); + + Trick::VariableServerSessionThread * vst = new Trick::VariableServerSessionThread() ; + + vst->set_connection(udp_conn); + vst->copy_cpus(listen_thread.get_cpus()) ; + vst->create_thread() ; + + message_publish(MSG_INFO, "Created UDP variable server %s: %d\n", set_address.c_str(), set_port); + + return 0 ; +} + +int Trick::VariableServer::create_multicast_socket(const char * mcast_address, const char * address, unsigned short in_port ) { + + // Multicast sockets are created without a listen thread, and represent only 1 session + // Create a VariableServerSessionThread to manage this session + + MulticastGroup * multicast = new MulticastGroup(); + message_publish(MSG_INFO, "Created UDP variable server %s: %d\n", address, in_port); + + int status = multicast->initialize_with_receiving(address, mcast_address, in_port); + if ( status != 0 ) { + delete multicast; + return 0; + } + + std::string set_address = multicast->getHostname(); + int set_port = multicast->getPort(); + + Trick::VariableServerSessionThread * vst = new Trick::VariableServerSessionThread() ; + + vst->set_connection(multicast); + vst->copy_cpus(listen_thread.get_cpus()) ; + vst->create_thread() ; + + message_publish(MSG_INFO, "Multicast variable server output %s:%d\n", mcast_address, set_port) ; + + return 0 ; +} + diff --git a/trick_source/sim_services/VariableServer/VariableServer_restart.cpp b/trick_source/sim_services/VariableServer/VariableServer_restart.cpp index dc312a3a..46b62ac0 100644 --- a/trick_source/sim_services/VariableServer/VariableServer_restart.cpp +++ b/trick_source/sim_services/VariableServer/VariableServer_restart.cpp @@ -9,9 +9,12 @@ int Trick::VariableServer::restart() { if ( listen_thread.get_pthread_id() == 0 ) { listen_thread.create_thread() ; } - std::map < pthread_t , VariableServerListenThread * >::iterator it ; - for( it = additional_listen_threads.begin() ; it != additional_listen_threads.end() ; it++ ) { - (*it).second->restart() ; + + for (const auto& listen_it : additional_listen_threads) { + listen_it.second->restart(); + if ( listen_it.second->get_pthread_id() == 0 ) { + listen_it.second->create_thread() ; + } } return 0 ; } @@ -23,17 +26,17 @@ int Trick::VariableServer::restart() { // Suspend variable server processing prior to reloading a checkpoint. int Trick::VariableServer::suspendPreCheckpointReload() { - std::map::iterator pos ; + // Pause listening on all listening threads listen_thread.pause_listening() ; for (const auto& listen_it : additional_listen_threads) { listen_it.second->pause_listening(); } + // Suspend session threads pthread_mutex_lock(&map_mutex) ; - for ( pos = var_server_threads.begin() ; pos != var_server_threads.end() ; pos++ ) { - VariableServerThread* vst = (*pos).second ; - vst->preload_checkpoint() ; + for (const auto& vst_it : var_server_threads ) { + vst_it.second->preload_checkpoint() ; } pthread_mutex_unlock(&map_mutex) ; @@ -42,16 +45,16 @@ int Trick::VariableServer::suspendPreCheckpointReload() { // Resume variable server processing after reloading a MemoryManager (ASCII) checkpoint. int Trick::VariableServer::resumePostCheckpointReload() { - std::map::iterator pos ; + std::map::iterator pos ; + // Resume all session threads pthread_mutex_lock(&map_mutex) ; - // For each Variable Server Thread ... - for ( pos = var_server_threads.begin() ; pos != var_server_threads.end() ; pos++ ) { - VariableServerThread* vst = (*pos).second ; - vst->restart() ; + for (const auto& vst_it : var_server_threads ) { + vst_it.second->restart() ; } pthread_mutex_unlock(&map_mutex) ; + // Restart listening on all listening threads listen_thread.restart_listening() ; for (const auto& listen_it : additional_listen_threads) { listen_it.second->restart_listening(); diff --git a/trick_source/sim_services/VariableServer/VariableServer_shutdown.cpp b/trick_source/sim_services/VariableServer/VariableServer_shutdown.cpp index 84dd71bc..499da7c9 100644 --- a/trick_source/sim_services/VariableServer/VariableServer_shutdown.cpp +++ b/trick_source/sim_services/VariableServer/VariableServer_shutdown.cpp @@ -2,13 +2,16 @@ #include "trick/VariableServer.hh" int Trick::VariableServer::shutdown() { + + // Shutdown all listen threads listen_thread.cancel_thread() ; - for (const auto& listen_it : additional_listen_threads) { + for (auto& listen_it : additional_listen_threads) { listen_it.second->cancel_thread(); } + // Shutdown all session threads pthread_mutex_lock(&map_mutex) ; - for (const auto& it : var_server_threads) { + for (auto& it : var_server_threads) { it.second->cancel_thread() ; } pthread_mutex_unlock(&map_mutex) ; diff --git a/trick_source/sim_services/VariableServer/exit_var_thread.cpp b/trick_source/sim_services/VariableServer/exit_var_thread.cpp index f8a02d85..80210980 100644 --- a/trick_source/sim_services/VariableServer/exit_var_thread.cpp +++ b/trick_source/sim_services/VariableServer/exit_var_thread.cpp @@ -1,13 +1,11 @@ #include "trick/VariableServer.hh" +// This should only be called from the VST itself void exit_var_thread(void *in_vst) { - Trick::VariableServerThread * vst = (Trick::VariableServerThread *) in_vst ; - Trick::VariableServer * vs = vst->get_vs() ; + Trick::VariableServerSessionThread * vst = (Trick::VariableServerSessionThread *) in_vst ; - if (vst->get_pthread_id() != pthread_self()) { - std::cerr << "exit_var_thread must be called from the variable server thread" << std::endl; - } + Trick::VariableServer * vs = vst->get_vs() ; vs->delete_session(vst->get_pthread_id()); diff --git a/trick_source/sim_services/VariableServer/test/Makefile b/trick_source/sim_services/VariableServer/test/Makefile index 43d77b14..7d7fdbff 100644 --- a/trick_source/sim_services/VariableServer/test/Makefile +++ b/trick_source/sim_services/VariableServer/test/Makefile @@ -8,12 +8,12 @@ include $(dir $(lastword $(MAKEFILE_LIST)))../../../../share/trick/makefiles/Makefile.common # Replace -isystem with -I so ICG doesn't skip Trick headers -TRICK_SYSTEM_CXXFLAGS := $(subst -isystem,-I,$(TRICK_SYSTEM_CXXFLAGS)) +TRICK_SYSTEM_CXXFLAGS := $(subst -isystem,-I,$(TRICK_SYSTEM_CXXFLAGS)) -Wno-unused-command-line-argument # Flags passed to the preprocessor. TRICK_CXXFLAGS += -I$(GTEST_HOME)/include -I$(TRICK_HOME)/include -g -Wall -Wextra -Wno-sign-compare -std=c++11 ${TRICK_SYSTEM_CXXFLAGS} ${TRICK_TEST_FLAGS} TRICK_LIBS = -L${TRICK_LIB_DIR} -ltrick_mm -ltrick_units -ltrick_comm -ltrick_pyip -ltrick -ltrick_mm -ltrick_units -ltrick_comm -ltrick_pyip -ltrick -ltrick_var_binary_parser -ltrick_connection_handlers -ltrick_comm -TRICK_EXEC_LINK_LIBS += -L${GTEST_HOME}/lib64 -L${GTEST_HOME}/lib -lgtest -lgtest_main -lpthread +TRICK_EXEC_LINK_LIBS += -L${GTEST_HOME}/lib64 -L${GTEST_HOME}/lib -lgtest -lgtest_main -lgmock -lpthread # All tests produced by this Makefile. Remember to add new tests you # created to the list. @@ -22,14 +22,13 @@ VARIABLE_REFERENCE_TESTS = VariableReference_test \ VariableReference_writeValueAscii_test \ VariableReference_writeValueBinary_test -VARIABLE_SESSION_TESTS = VariableServerSession_test +VARIABLE_SESSION_TESTS = VariableServerSession_test -TESTS = $(VARIABLE_REFERENCE_TESTS) $(VARIABLE_SESSION_TESTS) +TESTS = $(VARIABLE_REFERENCE_TESTS) $(VARIABLE_SESSION_TESTS) VariableServerSessionThread_test VariableServerListenThread_test VariableServer_test TEST_OBJS = $(addprefix $(OBJ_DIR)/, $(addsuffix .o, $(TESTS))) -TO_ICG = VariableReference_test \ - TestConnection +TO_ICG = VariableReference_test ICG_OBJS = $(addprefix $(OBJ_DIR)/io_, $(addsuffix .o, $(TO_ICG))) @@ -58,10 +57,20 @@ $(ICG_OBJS): $(OBJ_DIR)/io_%.o : %.hh $(OBJ_DIR) $(TRICK_CXX) $(TRICK_CXXFLAGS) -c io_src/io_$*.cpp -o $@ -L${TRICK_HOME}/lib_${TRICK_HOST_CPU} $(TRICK_LIBS) $(TRICK_EXEC_LINK_LIBS) $(VARIABLE_REFERENCE_TESTS): %: $(OBJ_DIR)/%.o $(OBJ_DIR)/io_VariableReference_test.o - $(TRICK_CXX) $(TRICK_SYSTEM_LDFLAGS) -o $@ $^ -L${TRICK_HOME}/lib_${TRICK_HOST_CPU} $(TRICK_LIBS) $(TRICK_EXEC_LINK_LIBS) + $(TRICK_CXX) $(TRICK_SYSTEM_LDFLAGS) $(TRICK_CXXFLAGS) -o $@ $^ -L${TRICK_HOME}/lib_${TRICK_HOST_CPU} $(TRICK_LIBS) $(TRICK_EXEC_LINK_LIBS) + +$(VARIABLE_SESSION_TESTS): %: $(OBJ_DIR)/%.o + $(TRICK_CXX) $(TRICK_SYSTEM_LDFLAGS) $(TRICK_CXXFLAGS) $^ -L${TRICK_HOME}/lib_${TRICK_HOST_CPU} $(TRICK_LIBS) $(TRICK_EXEC_LINK_LIBS) -o $@ + +VariableServerSessionThread_test: %: $(OBJ_DIR)/%.o + $(TRICK_CXX) $(TRICK_SYSTEM_LDFLAGS) $(TRICK_CXXFLAGS) -o $@ $^ -L${TRICK_HOME}/lib_${TRICK_HOST_CPU} $(TRICK_LIBS) $(TRICK_EXEC_LINK_LIBS) + +VariableServerListenThread_test: %: $(OBJ_DIR)/%.o + $(TRICK_CXX) $(TRICK_SYSTEM_LDFLAGS) $(TRICK_CXXFLAGS) -o $@ $^ -L${TRICK_HOME}/lib_${TRICK_HOST_CPU} $(TRICK_LIBS) $(TRICK_EXEC_LINK_LIBS) + +VariableServer_test: %: $(OBJ_DIR)/%.o + $(TRICK_CXX) $(TRICK_SYSTEM_LDFLAGS) $(TRICK_CXXFLAGS) -o $@ $^ -L${TRICK_HOME}/lib_${TRICK_HOST_CPU} $(TRICK_LIBS) $(TRICK_EXEC_LINK_LIBS) -$(VARIABLE_SESSION_TESTS): %: $(OBJ_DIR)/%.o $(OBJ_DIR)/io_TestConnection.o - $(TRICK_CXX) $(TRICK_SYSTEM_LDFLAGS) -o $@ $^ -L${TRICK_HOME}/lib_${TRICK_HOST_CPU} $(TRICK_LIBS) $(TRICK_EXEC_LINK_LIBS) code-coverage: test # Give rid of any old code-coverage HTML we may have. diff --git a/trick_source/sim_services/VariableServer/test/MockClientConnection.hh b/trick_source/sim_services/VariableServer/test/MockClientConnection.hh new file mode 100644 index 00000000..83b8d0c0 --- /dev/null +++ b/trick_source/sim_services/VariableServer/test/MockClientConnection.hh @@ -0,0 +1,24 @@ +#ifndef MOCK_CLIENT_CONNECTION_HH +#define MOCK_CLIENT_CONNECTION_HH + +#include "trick/ClientConnection.hh" +#include + +class MockClientConnection : public Trick::ClientConnection { + public: + MOCK_METHOD0(start, int()); + MOCK_METHOD1(write, int(const std::string& message)); + MOCK_METHOD2(write, int(char * message, int size)); + MOCK_METHOD2(read, int(std::string& message, int max_len)); + MOCK_METHOD0(disconnect, int()); + MOCK_METHOD1(setBlockMode, int(bool blocking)); + MOCK_METHOD0(isInitialized, bool()); + MOCK_METHOD0(restart, int()); + MOCK_METHOD0(getClientTag, std::string()); + MOCK_METHOD1(setClientTag, int(std::string tag)); + MOCK_METHOD0(getClientHostname, std::string()); + MOCK_METHOD0(getClientPort, int()); + +}; + +#endif \ No newline at end of file diff --git a/trick_source/sim_services/VariableServer/test/MockMulticastGroup.hh b/trick_source/sim_services/VariableServer/test/MockMulticastGroup.hh new file mode 100644 index 00000000..275eefa3 --- /dev/null +++ b/trick_source/sim_services/VariableServer/test/MockMulticastGroup.hh @@ -0,0 +1,18 @@ +#ifndef MOCK_MULTICAST_GROUP_HH +#define MOCK_MULTICAST_GROUP_HH + +#include "trick/MulticastGroup.hh" + +#include + +class MockMulticastGroup : public Trick::MulticastGroup { + public: + MOCK_METHOD1(broadcast, int(std::string)); + MOCK_METHOD2(addAddress, int(std::string, int)); + MOCK_METHOD0(restart, int()); + MOCK_METHOD0(isInitialized, bool()); + MOCK_METHOD0(initialize, int()); + MOCK_METHOD0(disconnect, int()); +}; + +#endif \ No newline at end of file diff --git a/trick_source/sim_services/VariableServer/test/MockTCPClientListener.hh b/trick_source/sim_services/VariableServer/test/MockTCPClientListener.hh new file mode 100644 index 00000000..cf457d06 --- /dev/null +++ b/trick_source/sim_services/VariableServer/test/MockTCPClientListener.hh @@ -0,0 +1,24 @@ +#ifndef MOCK_CLIENT_LISTENER_HH +#define MOCK_CLIENT_LISTENER_HH + +#include "trick/TCPClientListener.hh" + +#include + +class MockTCPClientListener : public Trick::TCPClientListener { + public: + MOCK_METHOD2(initialize, int(std::string, int)); + MOCK_METHOD0(initialize, int()); + MOCK_METHOD1(setBlockMode, int(bool)); + MOCK_METHOD0(checkForNewConnections, bool()); + MOCK_METHOD0(setUpNewConnection, Trick::TCPConnection*()); + MOCK_METHOD0(disconnect, int()); + MOCK_METHOD0(checkSocket, int()); + MOCK_METHOD1(validateSourceAddress, bool(std::string)); + MOCK_METHOD0(isInitialized, bool()); + MOCK_METHOD0(restart, int()); + MOCK_METHOD0(getHostname, std::string()); + MOCK_METHOD0(getPort, int()); +}; + +#endif \ No newline at end of file diff --git a/trick_source/sim_services/VariableServer/test/MockTCPConnection.hh b/trick_source/sim_services/VariableServer/test/MockTCPConnection.hh new file mode 100644 index 00000000..37a28456 --- /dev/null +++ b/trick_source/sim_services/VariableServer/test/MockTCPConnection.hh @@ -0,0 +1,21 @@ +#ifndef MOCK_TCP_CONNECTION_HH +#define MOCK_TCP_CONNECTION_HH + +#include "trick/TCPConnection.hh" +#include + +class MockTCPConnection : public Trick::TCPConnection { + public: + MOCK_METHOD0(start, int()); + MOCK_METHOD1(write, int(const std::string& message)); + MOCK_METHOD2(write, int(char * message, int size)); + MOCK_METHOD2(read, int(std::string& message, int max_len)); + MOCK_METHOD0(disconnect, int()); + MOCK_METHOD1(setBlockMode, int(bool blocking)); + MOCK_METHOD0(isInitialized, bool()); + MOCK_METHOD0(restart, int()); + MOCK_METHOD0(getClientTag, std::string()); + MOCK_METHOD1(setClientTag, int(std::string tag)); +}; + +#endif \ No newline at end of file diff --git a/trick_source/sim_services/VariableServer/test/MockVariableServerSession.hh b/trick_source/sim_services/VariableServer/test/MockVariableServerSession.hh new file mode 100644 index 00000000..8339a81d --- /dev/null +++ b/trick_source/sim_services/VariableServer/test/MockVariableServerSession.hh @@ -0,0 +1,33 @@ +#ifndef MOCK_VARIABLE_SERVER_SESSION_HH +#define MOCK_VARIABLE_SERVER_SESSION_HH + +#include "trick/VariableServerSession.hh" +#include + +class MockVariableServerSession : public Trick::VariableServerSession { + public: + MOCK_METHOD0(handle_message, int()); + MOCK_METHOD0(write_data, int()); + MOCK_METHOD0(get_exit_cmd, bool()); + MOCK_METHOD0(get_pause, bool()); + MOCK_CONST_METHOD0(get_enabled, bool()); + MOCK_CONST_METHOD0(get_write_mode, VS_WRITE_MODE()); + MOCK_CONST_METHOD0(get_copy_mode, VS_COPY_MODE()); + MOCK_METHOD0(set_log_on, int()); + MOCK_METHOD0(set_log_off, int()); + MOCK_METHOD0(copy_sim_data, int()); + MOCK_CONST_METHOD0(get_update_rate, double()); + MOCK_CONST_METHOD0(get_frame_multiple, int()); + MOCK_CONST_METHOD0(get_frame_offset, int()); + MOCK_CONST_METHOD0(get_freeze_frame_multiple, int()); + MOCK_CONST_METHOD0(get_freeze_frame_offset, int()); + MOCK_CONST_METHOD0(get_next_tics, long long ()); + MOCK_CONST_METHOD0(get_freeze_next_tics, long long()); + MOCK_METHOD0(set_exit_cmd, void()); + + MOCK_METHOD0(copy_and_write_async, int()); + + int copy_and_write_async_concrete() { return Trick::VariableServerSession::copy_and_write_async(); } +}; + +#endif \ No newline at end of file diff --git a/trick_source/sim_services/VariableServer/test/TestConnection.hh b/trick_source/sim_services/VariableServer/test/TestConnection.hh deleted file mode 100644 index 89eea807..00000000 --- a/trick_source/sim_services/VariableServer/test/TestConnection.hh +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef TEST_CONNECTION_HH -#define TEST_CONNECTION_HH - -#include "trick/ClientConnection.hh" -#include -#include -#include -#include - -class TestConnection : public Trick::ClientConnection { - public: - ~TestConnection () { - for (char * message : binary_messages_written) { - free (message); - } - } - - int initialize() { - valid = true; - } - - int write (const std::string& message) { - if (!valid) - return -1; - - ascii_messages_written.emplace_back(message); - return 0; - } - - int write (char * message, int size) { - if (!valid) - return -1; - - char * msg_copy = (char *) malloc(size+1); - memcpy(msg_copy, message, size+1); - binary_messages_written.push_back(msg_copy); - - return 0; - } - - std::string read (int max_len = MAX_CMD_LEN) { - if (queued_messages.empty()) { - return ""; - } - - std::string ret = queued_messages.front(); - queued_messages.pop(); - return ret; - } - - int disconnect () { - valid = false; - return 0; - } - - std::string get_client_tag () { - return client_tag; - } - - int set_client_tag(std::string tag) { - client_tag = tag; - } - - int set_block_mode (int mode) { - return 0; - } - - - std::queue queued_messages; - std::vector ascii_messages_written; - std::vector binary_messages_written; - - - std::string client_tag; - bool valid = true; - -}; - - -#endif \ No newline at end of file diff --git a/trick_source/sim_services/VariableServer/test/VariableReference_test.cc b/trick_source/sim_services/VariableServer/test/VariableReference_test.cc index 6ff68a5f..9c616441 100644 --- a/trick_source/sim_services/VariableServer/test/VariableReference_test.cc +++ b/trick_source/sim_services/VariableServer/test/VariableReference_test.cc @@ -9,7 +9,7 @@ TEST_F(VariableReference_test, getName) { // ACT // ASSERT - EXPECT_EQ(strcmp(ref.getName(), "test_int"), 0); + EXPECT_EQ(ref.getName(), std::string("test_int")); } TEST_F(VariableReference_test, validateAddress) { @@ -162,7 +162,7 @@ TEST_F(VariableReference_test, printWithoutUnits) { std::stringstream ss; // ACT - EXPECT_STREQ(ref.getBaseUnits(), "m"); + EXPECT_EQ(ref.getBaseUnits(), std::string("m")); // ASSERT // Doesn't actually print with units unless set diff --git a/trick_source/sim_services/VariableServer/test/VariableReference_writeValueBinary_test.cc b/trick_source/sim_services/VariableServer/test/VariableReference_writeValueBinary_test.cc index 7f4ba5a9..7cdc41e9 100644 --- a/trick_source/sim_services/VariableServer/test/VariableReference_writeValueBinary_test.cc +++ b/trick_source/sim_services/VariableServer/test/VariableReference_writeValueBinary_test.cc @@ -121,7 +121,7 @@ TEST_F(VariableReference_test, writeValueBinary_string) { unsigned char expected_bytes[6] = {0x61, 0x62, 0x63, 0x64, 0x65, 0x66}; // ASSERT - for (int i = 0; i < test_a.length(); i++) { + for (unsigned int i = 0; i < test_a.length(); i++) { EXPECT_EQ(static_cast(actual_bytes[i]), expected_bytes[i]); } } @@ -140,12 +140,12 @@ TEST_F(VariableReference_test, writeNameBinary) { ref.writeNameBinary(ss); // ASSERT - char * actual_bytes = (char *) malloc (sizeof(int) + strlen(ref.getName())); + char * actual_bytes = (char *) malloc (sizeof(int) + ref.getName().size()); ss.read(actual_bytes, sizeof(int) + 6); unsigned char expected_bytes[sizeof(int) + 6] = {0x06, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x61}; // ASSERT - for (int i = 0; i < sizeof(int) + strlen(ref.getName()); i++) { + for (int i = 0; i < sizeof(int) + ref.getName().size(); i++) { EXPECT_EQ(static_cast(actual_bytes[i]), expected_bytes[i]); } } diff --git a/trick_source/sim_services/VariableServer/test/VariableServerListenThread_test.cc b/trick_source/sim_services/VariableServer/test/VariableServerListenThread_test.cc new file mode 100644 index 00000000..dbfd409b --- /dev/null +++ b/trick_source/sim_services/VariableServer/test/VariableServerListenThread_test.cc @@ -0,0 +1,392 @@ +/******************************TRICK HEADER************************************* +PURPOSE: ( Tests for the VariableServerListenThread class ) +*******************************************************************************/ + +#include +#include + +#include "trick/Executive.hh" +#include "trick/CommandLineArguments.hh" +#include "trick/VariableServer.hh" + +#include "trick/VariableServerListenThread.hh" + +#include "MockTCPClientListener.hh" +#include "MockTCPConnection.hh" +#include "MockMulticastGroup.hh" + +using ::testing::Return; +using ::testing::_; +using ::testing::AtLeast; + + +/* + Test Fixture. + */ +class VariableServerListenThread_test : public ::testing::Test { + protected: + // Static global dependencies that I would like to eventually mock out + Trick::Executive * executive; + Trick::CommandLineArguments * cmd_args; + Trick::VariableServer * varserver; + + // Listener + MockTCPClientListener * listener; + MockMulticastGroup * mcast; + + VariableServerListenThread_test() { + // Set up dependencies that haven't been broken + executive = new Trick::Executive; + cmd_args = new Trick::CommandLineArguments; + varserver = new Trick::VariableServer; + Trick::VariableServerSessionThread::set_vs_ptr(varserver); + + // Set up mocks + listener = new MockTCPClientListener; + mcast = new MockMulticastGroup; + } + + ~VariableServerListenThread_test() { + delete executive; + delete cmd_args; + delete varserver; + } + + void SetUp() {} + void TearDown() {} +}; + +void setup_normal_listener_expectations (MockTCPClientListener * listener) { + // Starting the connection succeeds + EXPECT_CALL(*listener, getHostname()) + .WillRepeatedly(Return("MyHostname")); + EXPECT_CALL(*listener, getPort()) + .WillRepeatedly(Return(1234)); +} + +void setup_normal_mcast_expectations (MockMulticastGroup * mcast) { + EXPECT_CALL(*mcast, initialize()) + .WillOnce(Return(0)); + + EXPECT_CALL(*mcast, isInitialized()) + .WillRepeatedly(Return(1)); + + EXPECT_CALL(*mcast, addAddress(_, _)) + .Times(2); + + EXPECT_CALL(*mcast, broadcast(_)) + .Times(AtLeast(1)); +} + +TEST_F(VariableServerListenThread_test, init_listen_device) { + // ARRANGE + setup_normal_listener_expectations(listener); + EXPECT_CALL(*listener, initialize()) + .WillOnce(Return(0)); + + Trick::VariableServerListenThread listen_thread (listener); + + // ACT + listen_thread.init_listen_device(); + + // ASSERT +} + +TEST_F(VariableServerListenThread_test, init_listen_device_fails) { + // ARRANGE + setup_normal_listener_expectations(listener); + EXPECT_CALL(*listener, initialize()) + .WillOnce(Return(-1)); + + Trick::VariableServerListenThread listen_thread (listener); + + // ACT + int status = listen_thread.init_listen_device(); + + // ASSERT + EXPECT_EQ(status, -1); +} + + + +TEST_F(VariableServerListenThread_test, get_hostname) { + // ARRANGE + setup_normal_listener_expectations(listener); + + Trick::VariableServerListenThread listen_thread (listener); + + // ACT + const char * hostname = listen_thread.get_hostname(); + + // ASSERT + EXPECT_STREQ(hostname, "MyHostname"); +} + +TEST_F(VariableServerListenThread_test, get_port) { + // ARRANGE + setup_normal_listener_expectations(listener); + + Trick::VariableServerListenThread listen_thread (listener); + + // ACT + int port = listen_thread.get_port(); + + // ASSERT + EXPECT_EQ(port, 1234); +} + +TEST_F(VariableServerListenThread_test, get_port_returns_requested) { + // ARRANGE + setup_normal_listener_expectations(listener); + + Trick::VariableServerListenThread listen_thread (listener); + + // ACT + listen_thread.set_port(4321); + int port = listen_thread.get_port(); + + // ASSERT + EXPECT_EQ(port, 4321); +} + +TEST_F(VariableServerListenThread_test, check_and_move_listen_device_init_fails) { + // ARRANGE + setup_normal_listener_expectations(listener); + Trick::VariableServerListenThread listen_thread (listener); + listen_thread.set_port(4321); + + EXPECT_CALL(*listener, disconnect()); + EXPECT_CALL(*listener, initialize(_, _)) + .WillOnce(Return(1)); + + // ACT + int status = listen_thread.check_and_move_listen_device(); + + // ASSERT + EXPECT_EQ(status, -1); +} + +TEST_F(VariableServerListenThread_test, check_and_move_listen_device) { + // ARRANGE + setup_normal_listener_expectations(listener); + Trick::VariableServerListenThread listen_thread (listener); + listen_thread.set_port(4321); + + EXPECT_CALL(*listener, disconnect()); + EXPECT_CALL(*listener, initialize(_, _)) + .WillOnce(Return(0)); + + // ACT + int status = listen_thread.check_and_move_listen_device(); + + // ASSERT + EXPECT_EQ(status, 0); +} + +TEST_F(VariableServerListenThread_test, run_thread) { + // ARRANGE + setup_normal_listener_expectations(listener); + setup_normal_mcast_expectations(mcast); + + EXPECT_CALL(*listener, setBlockMode(true)) + .Times(1); + + EXPECT_CALL(*listener, checkForNewConnections()) + .WillRepeatedly(Return(false)); + + Trick::VariableServerListenThread listen_thread (listener); + listen_thread.set_multicast_group(mcast); + + // ACT + listen_thread.create_thread(); + + sleep(3); + + listen_thread.cancel_thread(); + listen_thread.join_thread(); + + // ASSERT +} + + +TEST_F(VariableServerListenThread_test, run_thread_no_broadcast) { + // ARRANGE + setup_normal_listener_expectations(listener); + + // Expect no calls to mcast + EXPECT_CALL (*mcast, initialize()) + .Times(0); + + EXPECT_CALL (*mcast, broadcast(_)) + .Times(0); + + EXPECT_CALL(*listener, setBlockMode(true)) + .Times(1); + + EXPECT_CALL(*listener, checkForNewConnections()) + .WillRepeatedly(Return(false)); + + Trick::VariableServerListenThread listen_thread (listener); + listen_thread.set_broadcast(false); + listen_thread.set_multicast_group(mcast); + + // ACT + listen_thread.create_thread(); + + sleep(3); + + listen_thread.cancel_thread(); + listen_thread.join_thread(); + + // ASSERT +} + + + +TEST_F(VariableServerListenThread_test, run_thread_turn_on_broadcast) { + // ARRANGE + setup_normal_listener_expectations(listener); + setup_normal_mcast_expectations(mcast); + + EXPECT_CALL(*listener, setBlockMode(true)) + .Times(1); + + EXPECT_CALL(*listener, checkForNewConnections()) + .WillRepeatedly(Return(false)); + + EXPECT_CALL(*mcast, isInitialized()) + .WillOnce(Return(0)) + .WillRepeatedly(Return(1)); + + Trick::VariableServerListenThread listen_thread (listener); + listen_thread.set_broadcast(false); + listen_thread.set_multicast_group(mcast); + + // ACT + listen_thread.create_thread(); + + EXPECT_EQ(listen_thread.get_broadcast(), false); + + sleep(3); + listen_thread.set_broadcast(true); + sleep(3); + EXPECT_EQ(listen_thread.get_broadcast(), true); + + + listen_thread.cancel_thread(); + listen_thread.join_thread(); + + // ASSERT +} + +TEST_F(VariableServerListenThread_test, accept_connection) { + // ARRANGE + setup_normal_listener_expectations(listener); + + // Expect no calls to mcast + EXPECT_CALL (*mcast, initialize()) + .Times(0); + + EXPECT_CALL (*mcast, broadcast(_)) + .Times(0); + + EXPECT_CALL(*listener, setBlockMode(true)) + .Times(1); + + EXPECT_CALL(*listener, checkForNewConnections()) + .WillOnce(Return(true)) + .WillRepeatedly(Return(false)); + + MockTCPConnection connection; + EXPECT_CALL(connection, start()) + .WillOnce(Return(0)); + + EXPECT_CALL(connection, read(_, _)) + .WillOnce(Return(-1)); + + EXPECT_CALL(connection, disconnect()); + + EXPECT_CALL(*listener, setUpNewConnection()) + .WillOnce(Return(&connection)); + + Trick::VariableServerListenThread listen_thread (listener); + listen_thread.set_broadcast(false); + listen_thread.set_multicast_group(mcast); + + // ACT + listen_thread.create_thread(); + + listen_thread.dump(std::cout); + + sleep(3); + + listen_thread.cancel_thread(); + listen_thread.join_thread(); + + // ASSERT +} + + +TEST_F(VariableServerListenThread_test, connection_fails) { + // ARRANGE + setup_normal_listener_expectations(listener); + + // Expect no calls to mcast + EXPECT_CALL (*mcast, initialize()) + .Times(0); + + EXPECT_CALL (*mcast, broadcast(_)) + .Times(0); + + EXPECT_CALL(*listener, setBlockMode(true)) + .Times(1); + + EXPECT_CALL(*listener, checkForNewConnections()) + .WillOnce(Return(true)) + .WillRepeatedly(Return(false)); + + MockTCPConnection connection; + EXPECT_CALL(connection, start()) + .WillOnce(Return(-1)); + + EXPECT_CALL(connection, disconnect()); + + EXPECT_CALL(*listener, setUpNewConnection()) + .WillOnce(Return(&connection)); + + Trick::VariableServerListenThread listen_thread (listener); + listen_thread.set_broadcast(false); + listen_thread.set_multicast_group(mcast); + + // ACT + listen_thread.create_thread(); + + sleep(3); + + listen_thread.cancel_thread(); + listen_thread.join_thread(); + + // ASSERT +} + + +TEST_F(VariableServerListenThread_test, restart_fails) { + // ARRANGE + + setup_normal_listener_expectations(listener); + Trick::VariableServerListenThread listen_thread (listener); + + EXPECT_CALL(*listener, restart()); + EXPECT_CALL(*listener, validateSourceAddress(_)) + .WillOnce(Return(false)); + + EXPECT_CALL(*listener, initialize(_, _)) + .WillOnce(Return(-1)); + + EXPECT_CALL(*listener, disconnect()); + + + // ACT + // ASSERT + EXPECT_EQ(listen_thread.restart(), -1); +} \ No newline at end of file diff --git a/trick_source/sim_services/VariableServer/test/VariableServerSessionThread_test.cc b/trick_source/sim_services/VariableServer/test/VariableServerSessionThread_test.cc new file mode 100644 index 00000000..5b9cb609 --- /dev/null +++ b/trick_source/sim_services/VariableServer/test/VariableServerSessionThread_test.cc @@ -0,0 +1,357 @@ +/******************************TRICK HEADER************************************* +PURPOSE: ( Tests for the VariableServerSessionThread class ) +*******************************************************************************/ + +#include +#include +#include + +#include "trick/VariableServer.hh" +#include "trick/RealtimeSync.hh" +#include "trick/GetTimeOfDayClock.hh" +#include "trick/ITimer.hh" +#include "trick/ExecutiveException.hh" + +#include "trick/VariableServerSessionThread.hh" + +#include "MockVariableServerSession.hh" +#include "MockClientConnection.hh" + +using ::testing::Return; +using ::testing::_; +using ::testing::AtLeast; +using ::testing::DoAll; +using ::testing::Throw; +using ::testing::Const; +using ::testing::NiceMock; + + +// Set up default mock behavior + +void setup_default_connection_mocks (MockClientConnection * connection) { + // Starting the connection succeeds + ON_CALL(*connection, start()) + .WillByDefault(Return(0)); + + // We should always get a disconnect call + ON_CALL(*connection, disconnect()) + .WillByDefault(Return(0)); +} + +void setup_default_session_mocks (MockVariableServerSession * session, bool command_exit = true) { + ON_CALL(*session, handle_message()) + .WillByDefault(Return(0)); + + ON_CALL(*session, copy_and_write_async()) + .WillByDefault(Return(0)); + + ON_CALL(*session, get_pause()) + .WillByDefault(Return(false)); + + ON_CALL(Const(*session), get_update_rate()) + .WillByDefault(Return(0.001)); + + ON_CALL(*session, get_exit_cmd()) + .WillByDefault(Return(false)); +} + +/* + Test Fixture. + */ +class VariableServerSessionThread_test : public ::testing::Test { + protected: + // Static global dependencies that I would like to eventually mock out + Trick::VariableServer * varserver; + Trick::RealtimeSync * realtime_sync; + Trick::GetTimeOfDayClock clock; + Trick::ITimer timer; + + MockClientConnection connection; + NiceMock * session; + + VariableServerSessionThread_test() { + // Set up dependencies that haven't been broken + varserver = new Trick::VariableServer; + Trick::VariableServerSessionThread::set_vs_ptr(varserver); + realtime_sync = new Trick::RealtimeSync(&clock, &timer); + + // Set up mocks + session = new NiceMock; + setup_default_connection_mocks(&connection); + setup_default_session_mocks(session); + } + + ~VariableServerSessionThread_test() { + delete varserver; + delete realtime_sync; + } + + void SetUp() {} + void TearDown() {} +}; + + +// Helper functions for common cases of Mock expectations + +void setup_normal_connection_expectations (MockClientConnection * connection) { + // Starting the connection succeeds + EXPECT_CALL(*connection, start()) + .Times(1) + .WillOnce(Return(0)); + + // We should always get a disconnect call + EXPECT_CALL(*connection, disconnect()) + .Times(1); +} + +void set_session_exit_after_some_loops(MockVariableServerSession * session) { + EXPECT_CALL(*session, get_exit_cmd()) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(true)); +} + +TEST_F(VariableServerSessionThread_test, connection_failure) { + // ARRANGE + + // Starting the connection fails + EXPECT_CALL(connection, start()) + .Times(1) + .WillOnce(Return(1)); + + // Set up VariableServerSessionThread + Trick::VariableServerSessionThread * vst = new Trick::VariableServerSessionThread(session) ; + vst->set_connection(&connection); + + // ACT + vst->create_thread(); + pthread_t id = vst->get_pthread_id(); + Trick::ConnectionStatus status = vst->wait_for_accept(); + vst->join_thread(); + + // ASSERT + EXPECT_EQ(status, Trick::ConnectionStatus::CONNECTION_FAIL); + + // There should be nothing in the VariableServer's thread list + EXPECT_EQ(varserver->get_vst(id), (Trick::VariableServerSessionThread *) NULL); + EXPECT_EQ(varserver->get_session(id), (Trick::VariableServerSession *) NULL); + + delete session; +} + + +TEST_F(VariableServerSessionThread_test, exit_if_handle_message_fails) { + // ARRANGE + setup_normal_connection_expectations(&connection); + + // Handle a message, but it fails + EXPECT_CALL(*session, handle_message()) + .Times(1) + .WillOnce(Return(-1)); + + + // Set up VariableServerSessionThread + Trick::VariableServerSessionThread * vst = new Trick::VariableServerSessionThread(session) ; + vst->set_connection(&connection); + + // ACT + vst->create_thread(); + pthread_t id = vst->get_pthread_id(); + Trick::ConnectionStatus status = vst->wait_for_accept(); + ASSERT_EQ(status, Trick::ConnectionStatus::CONNECTION_SUCCESS); + + vst->join_thread(); + + // ASSERT + + // There should be nothing in the VariableServer's thread list + EXPECT_EQ(varserver->get_vst(id), (Trick::VariableServerSessionThread *) NULL); + EXPECT_EQ(varserver->get_session(id), (Trick::VariableServerSession *) NULL); +} + + +TEST_F(VariableServerSessionThread_test, exit_if_write_fails) { + // ARRANGE + setup_normal_connection_expectations(&connection); + + // Write out data + EXPECT_CALL(*session, copy_and_write_async()) + .WillOnce(Return(-1)); + + + // Set up VariableServerSessionThread + Trick::VariableServerSessionThread * vst = new Trick::VariableServerSessionThread(session) ; + vst->set_connection(&connection); + + // ACT + vst->create_thread(); + pthread_t id = vst->get_pthread_id(); + Trick::ConnectionStatus status = vst->wait_for_accept(); + ASSERT_EQ(status, Trick::ConnectionStatus::CONNECTION_SUCCESS); + + vst->join_thread(); + + // ASSERT + + // There should be nothing in the VariableServer's thread list + EXPECT_EQ(varserver->get_vst(id), (Trick::VariableServerSessionThread *) NULL); + EXPECT_EQ(varserver->get_session(id), (Trick::VariableServerSession *) NULL); +} + +TEST_F(VariableServerSessionThread_test, exit_commanded) { + // ARRANGE + setup_normal_connection_expectations(&connection); + set_session_exit_after_some_loops(session); + + // Set up VariableServerSessionThread + Trick::VariableServerSessionThread * vst = new Trick::VariableServerSessionThread(session) ; + vst->set_connection(&connection); + + // ACT + vst->create_thread(); + pthread_t id = vst->get_pthread_id(); + Trick::ConnectionStatus status = vst->wait_for_accept(); + ASSERT_EQ(status, Trick::ConnectionStatus::CONNECTION_SUCCESS); + + // Confirm that the session has been created + Trick::VariableServerSession * vs_session = varserver->get_session(id); + ASSERT_TRUE(vs_session == session); + + // Runs for a few loops, then exits + + // Thread should shut down + vst->join_thread(); + + // ASSERT + // There should be nothing in the VariableServer's thread list + EXPECT_EQ(varserver->get_vst(id), (Trick::VariableServerSessionThread *) NULL); + EXPECT_EQ(varserver->get_session(id), (Trick::VariableServerSession *) NULL); +} + +TEST_F(VariableServerSessionThread_test, thread_cancelled) { + // ARRANGE + setup_normal_connection_expectations(&connection); + + // Set up VariableServerSessionThread + Trick::VariableServerSessionThread * vst = new Trick::VariableServerSessionThread(session) ; + vst->set_connection(&connection); + vst->create_thread(); + pthread_t id = vst->get_pthread_id(); + Trick::ConnectionStatus status = vst->wait_for_accept(); + ASSERT_EQ(status, Trick::ConnectionStatus::CONNECTION_SUCCESS); + + // Confirm that the session has been created + Trick::VariableServerSession * vs_session = varserver->get_session(id); + ASSERT_TRUE(vs_session == session); + + // ACT + vst->cancel_thread(); + + // Thread should shut down + vst->join_thread(); + + // ASSERT + // There should be nothing in the VariableServer's thread list + EXPECT_EQ(varserver->get_vst(id), (Trick::VariableServerSessionThread *) NULL); + EXPECT_EQ(varserver->get_session(id), (Trick::VariableServerSession *) NULL); +} + + +TEST_F(VariableServerSessionThread_test, turn_session_log_on) { + // ARRANGE + setup_normal_connection_expectations(&connection); + set_session_exit_after_some_loops(session); + + varserver->set_var_server_log_on(); + + // We expect a the session's log to be turned on + EXPECT_CALL(*session, set_log_on()) + .Times(1); + + // Set up VariableServerSessionThread + Trick::VariableServerSessionThread * vst = new Trick::VariableServerSessionThread(session) ; + vst->set_connection(&connection); + + // ACT + vst->create_thread(); + pthread_t id = vst->get_pthread_id(); + Trick::ConnectionStatus status = vst->wait_for_accept(); + ASSERT_EQ(status, Trick::ConnectionStatus::CONNECTION_SUCCESS); + + // Confirm that the session has been created + Trick::VariableServerSession * vs_session = varserver->get_session(id); + ASSERT_TRUE(vs_session == session); + + // Thread should shut down + vst->join_thread(); + + // ASSERT + // There should be nothing in the VariableServer's thread list + EXPECT_EQ(varserver->get_vst(id), (Trick::VariableServerSessionThread *) NULL); + EXPECT_EQ(varserver->get_session(id), (Trick::VariableServerSession *) NULL); +} + +TEST_F(VariableServerSessionThread_test, throw_trick_executive_exception) { + // ARRANGE + setup_normal_connection_expectations(&connection); + + EXPECT_CALL(*session, get_exit_cmd()) + .WillRepeatedly(Return(false)); + + // Set up VariableServerSessionThread + Trick::VariableServerSessionThread * vst = new Trick::VariableServerSessionThread(session) ; + vst->set_connection(&connection); + + EXPECT_CALL(*session, handle_message()) + .WillOnce(Throw(Trick::ExecutiveException(-1, __FILE__, __LINE__, "Trick::ExecutiveException Error message for testing"))); + + // ACT + vst->create_thread(); + pthread_t id = vst->get_pthread_id(); + Trick::ConnectionStatus status = vst->wait_for_accept(); + ASSERT_EQ(status, Trick::ConnectionStatus::CONNECTION_SUCCESS); + + // Confirm that the session has been created + Trick::VariableServerSession * vs_session = varserver->get_session(id); + ASSERT_TRUE(vs_session == session); + + // Thread should shut down + vst->join_thread(); + + // ASSERT + // There should be nothing in the VariableServer's thread list + EXPECT_EQ(varserver->get_vst(id), (Trick::VariableServerSessionThread *) NULL); + EXPECT_EQ(varserver->get_session(id), (Trick::VariableServerSession *) NULL); +} + +TEST_F(VariableServerSessionThread_test, throw_exception) { + // ARRANGE + setup_normal_connection_expectations(&connection); + + EXPECT_CALL(*session, get_exit_cmd()) + .WillRepeatedly(Return(false)); + + // Set up VariableServerSessionThread + Trick::VariableServerSessionThread * vst = new Trick::VariableServerSessionThread(session) ; + vst->set_connection(&connection); + + EXPECT_CALL(*session, handle_message()) + .WillOnce(Throw(std::logic_error("Error message for testing"))); + + // ACT + vst->create_thread(); + pthread_t id = vst->get_pthread_id(); + Trick::ConnectionStatus status = vst->wait_for_accept(); + ASSERT_EQ(status, Trick::ConnectionStatus::CONNECTION_SUCCESS); + + // Thread should shut down + vst->join_thread(); + + // ASSERT + // There should be nothing in the VariableServer's thread list + EXPECT_EQ(varserver->get_vst(id), (Trick::VariableServerSessionThread *) NULL); + EXPECT_EQ(varserver->get_session(id), (Trick::VariableServerSession *) NULL); +} \ No newline at end of file diff --git a/trick_source/sim_services/VariableServer/test/VariableServerSession_test.cc b/trick_source/sim_services/VariableServer/test/VariableServerSession_test.cc index 66069a16..3a702c99 100644 --- a/trick_source/sim_services/VariableServer/test/VariableServerSession_test.cc +++ b/trick_source/sim_services/VariableServer/test/VariableServerSession_test.cc @@ -3,6 +3,8 @@ PURPOSE: ( Tests for the VariableServerSession class ) *******************************************************************************/ #include +#include + #include #include #include @@ -11,10 +13,25 @@ PURPOSE: ( Tests for the VariableServerSession class ) #include "trick/MemoryManager.hh" #include "trick/UdUnits.hh" -#include "TestConnection.hh" +#include "trick/RealtimeSync.hh" +#include "trick/GetTimeOfDayClock.hh" +#include "trick/ITimer.hh" + #include "trick/VariableServerSession.hh" #include "trick/var_binary_parser.hh" +#include "MockClientConnection.hh" +#include "MockVariableServerSession.hh" + + + +using ::testing::AtLeast; +using ::testing::_; +using ::testing::Truly; +using ::testing::Args; +using ::testing::Return; +using ::testing::Invoke; + /* Test Fixture. @@ -23,16 +40,26 @@ class VariableServerSession_test : public ::testing::Test { protected: Trick::MemoryManager *memmgr; Trick::UdUnits * udunits; + Trick::RealtimeSync * realtime_sync; - TestConnection connection; + Trick::GetTimeOfDayClock clock; + Trick::ITimer timer; + + + MockClientConnection connection; VariableServerSession_test() { memmgr = new Trick::MemoryManager; udunits = new Trick::UdUnits; + realtime_sync = new Trick::RealtimeSync(&clock, &timer); udunits->read_default_xml(); } - ~VariableServerSession_test() { delete memmgr; } + ~VariableServerSession_test() { + delete memmgr; + delete realtime_sync; + } + void SetUp() {} void TearDown() {} }; @@ -46,7 +73,7 @@ TEST_F(VariableServerSession_test, toString) { (void) memmgr->declare_extern_var(&b, "double b"); (void) memmgr->declare_extern_var(&c, "std::string c"); - Trick::VariableServerSession session(&connection); + Trick::VariableServerSession session; session.var_add("a"); session.var_add("b"); @@ -68,8 +95,7 @@ TEST_F(VariableServerSession_test, toString) { TEST_F(VariableServerSession_test, var_sync) { // ARRANGE - Trick::VariableServerSession session(&connection); - + Trick::VariableServerSession session; // ACT session.var_sync(0); @@ -94,7 +120,8 @@ TEST_F(VariableServerSession_test, var_sync) { TEST_F(VariableServerSession_test, large_message_ascii) { // ARRANGE - Trick::VariableServerSession session(&connection); + Trick::VariableServerSession session; + session.set_connection(&connection); const static int big_arr_size = 4000; // Make an array too big to fit in a single message @@ -104,49 +131,75 @@ TEST_F(VariableServerSession_test, large_message_ascii) { big_arr[i] = i; } + // Set it up with the memory manager (void) memmgr->declare_extern_var(&big_arr, "int big_arr[4000]"); + // Create references for all of them + std::vector vars; for (int i = 0; i < big_arr_size; i++) { std::string var_name = "big_arr[" + std::to_string(i) + "]"; - session.var_add(var_name.c_str()); + Trick::VariableReference * var = new Trick::VariableReference(var_name); + var->stageValue(); + vars.push_back(var); } - // ACT - session.copy_sim_data(); - session.write_data(); - - // ASSERT - ASSERT_TRUE(connection.ascii_messages_written.size() > 1); - int counter = 0; - - int message_index = 0; - for (int i = 0; i < connection.ascii_messages_written.size(); i++) { - std::string message = connection.ascii_messages_written[i]; - std::stringstream ss(message); + // Make a matcher for the Mock + // This will check that the messages that are passed into connection.write() are correct + int val_counter = 0; + int message_counter = 0; + int final_val = 0; + auto constructedCorrectly = [&] (std::string s) -> bool { + std::stringstream ss(s); std::string token; - // First val in first message should be the message type, 0 - if (i == 0) { + if (message_counter == 0) { + // First val in first message should be the message type, 0 std::getline(ss, token, '\t'); int message_type = stoi(token); - EXPECT_EQ(message_type, 0); + if (message_type != 0) return false; + } else { + // First val in all other message should be (last val in previous message)+1 + std::getline(ss, token, '\t'); + int first_num = stoi(token); + if (first_num != final_val+1) return false; + val_counter++; } + int num = 0; + + // The rest of the message should just be sequential while (std::getline(ss, token, '\t')) { if (token == "\n") { break; } - - int num = stoi(token); - EXPECT_EQ(counter, num); - counter++; + + num = stoi(token); + if (val_counter != num) return false; + val_counter++; } - } - EXPECT_EQ(counter, big_arr_size); + // Save the final value so that we can verify the next message starts correctly + final_val = num; + message_counter++; + + return true; + }; + + // Set up the mock connection - it should make 3 calls to write + // The constructedCorrectly matcher will ensure that the values passed in are what we want them to be + EXPECT_CALL(connection, write(Truly(constructedCorrectly))) + .Times(3); + + // ACT + session.write_data(vars, (VS_MESSAGE_TYPE) 0); + + // ASSERT + // Make sure that the entire message was written out + ASSERT_EQ(val_counter, big_arr_size); } -TEST_F(VariableServerSession_test, large_message_binary) { +TEST_F(VariableServerSession_test, DISABLED_large_message_binary) { // ARRANGE - Trick::VariableServerSession session(&connection); + Trick::VariableServerSession session; + session.set_connection(&connection); session.var_binary(); const static int big_arr_size = 4000; @@ -157,26 +210,57 @@ TEST_F(VariableServerSession_test, large_message_binary) { big_arr[i] = i; } + // Set it up with the memory manager (void) memmgr->declare_extern_var(&big_arr, "int big_arr[4000]"); + // Create references for all of them + std::vector vars; for (int i = 0; i < big_arr_size; i++) { std::string var_name = "big_arr[" + std::to_string(i) + "]"; - session.var_add(var_name.c_str()); + Trick::VariableReference * var = new Trick::VariableReference(var_name); + var->stageValue(); + vars.push_back(var); } + // Create a matcher that parses and collects the arguments into a ParsedBinaryMessage + ParsedBinaryMessage full_message; + auto binaryConstructedCorrectly = [&] (std::tuple msg_tuple) -> bool { + char * message; + int size; + std::tie(message, size) = msg_tuple; + ParsedBinaryMessage partial_message; + + // Put the message into a vector the parser + std::vector bytes; + for (int i = 0; i < size; i++) { + bytes.push_back(message[i]); + } + + // Parse and add it to the full message + try { + partial_message.parse(bytes); + full_message.combine(partial_message); + } catch (const MalformedMessageException& ex) { + std::cout << "Parser failed with message: " << ex.what(); + return false; + } + + return true; + }; + + // Set up the mock connection - it should make a bunch of calls to write + // The constructedCorrectly matcher will ensure that the values passed in are what we want them to be + EXPECT_CALL(connection, write(_, _)).With(Args<0,1>(Truly(binaryConstructedCorrectly))).Times(AtLeast(3)); + // ACT - session.copy_sim_data(); - session.write_data(); + session.write_data(vars, (VS_MESSAGE_TYPE) 0); // ASSERT - ParsedBinaryMessage full_message; - for (int i = 0; i < connection.binary_messages_written.size(); i++) { - ParsedBinaryMessage partial_message; - partial_message.parse(connection.binary_messages_written[i]); - full_message.combine(partial_message); - } - + + // Make sure we got all of the values ASSERT_EQ(full_message.getNumVars(), big_arr_size); + + // Make sure all of the values are correct and have names for (int i = 0; i < big_arr_size; i++) { try { std::string var_name = "big_arr[" + std::to_string(i) + "]"; @@ -188,3 +272,985 @@ TEST_F(VariableServerSession_test, large_message_binary) { } } } + + +void setup_partial_session_mock(MockVariableServerSession& session) { + EXPECT_CALL(session, copy_and_write_async()) + .WillOnce(Invoke(&session, &MockVariableServerSession::copy_and_write_async_concrete)); +} + +void set_session_modes(MockVariableServerSession& session, VS_COPY_MODE copy_mode, VS_WRITE_MODE write_mode, bool pause) { + EXPECT_CALL(session, get_copy_mode()) + .WillRepeatedly(Return(copy_mode)); + EXPECT_CALL(session, get_write_mode()) + .WillRepeatedly(Return(write_mode)); + EXPECT_CALL(session, get_pause()) + .WillOnce(Return(pause)); +} + + +TEST_F(VariableServerSession_test, copy_async_disabled) { + // ARRANGE + MockVariableServerSession session; + setup_partial_session_mock(session); + + // set disabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(false)); + + // Nothing else should be called + EXPECT_CALL(session, copy_sim_data()) + .Times(0); + + EXPECT_CALL(session, write_data()) + .Times(0); + + session.copy_and_write_async(); +} + + +TEST_F(VariableServerSession_test, copy_async_copy_and_write) { + // ARRANGE + MockVariableServerSession session; + setup_partial_session_mock(session); + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + // Copy async, write when copied, not paused + set_session_modes(session, VS_COPY_ASYNC, VS_WRITE_ASYNC, false); + + // Copy and write should be called + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(1); + + // ACT + int result = session.copy_and_write_async(); + + // ASSERT + ASSERT_GE(result, 0); +} + +TEST_F(VariableServerSession_test, copy_async_copy_write_when_copied) { + // ARRANGE + MockVariableServerSession session; + setup_partial_session_mock(session); + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + // Copy async, write when copied, not paused + set_session_modes(session, VS_COPY_ASYNC, VS_WRITE_WHEN_COPIED, false); + + // Copy and write should be called + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(1); + + // ACT + int result = session.copy_and_write_async(); + + // ASSERT + ASSERT_GE(result, 0); +} + +TEST_F(VariableServerSession_test, copy_async_no_copy_or_write) { + // ARRANGE + MockVariableServerSession session; + setup_partial_session_mock(session); + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + // Copy async, write when copied, not paused + set_session_modes(session, VS_COPY_SCHEDULED, VS_WRITE_WHEN_COPIED, false); + realtime_sync->active = true; + + + // Copy and write should be called + EXPECT_CALL(session, copy_sim_data()) + .Times(0); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_async(); + + // ASSERT + ASSERT_GE(result, 0); +} + +TEST_F(VariableServerSession_test, copy_async_non_realtime) { + // ARRANGE + MockVariableServerSession session; + setup_partial_session_mock(session); + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + // Copy async, write async, paused + set_session_modes(session, VS_COPY_SCHEDULED, VS_WRITE_WHEN_COPIED, false); + realtime_sync->active = false; + + // Copy and write should be called + EXPECT_CALL(session, copy_sim_data()) + .Times(0); + + EXPECT_CALL(session, write_data()) + .Times(1); + + // ACT + int result = session.copy_and_write_async(); + + // ASSERT + ASSERT_GE(result, 0); +} + +TEST_F(VariableServerSession_test, copy_async_paused) { + // ARRANGE + MockVariableServerSession session; + setup_partial_session_mock(session); + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + // Copy async, write async, paused + set_session_modes(session, VS_COPY_ASYNC, VS_WRITE_ASYNC, true); + + // Copy and write should be called + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_async(); + + // ASSERT + ASSERT_GE(result, 0); +} + + +TEST_F(VariableServerSession_test, copy_async_write_fails) { + // ARRANGE + MockVariableServerSession session; + setup_partial_session_mock(session); + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + // Copy async, write async, paused + set_session_modes(session, VS_COPY_ASYNC, VS_WRITE_ASYNC, false); + + // Copy and write should be called + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .WillOnce(Return(-1)); + + // Exit should be called + EXPECT_CALL(session, set_exit_cmd()) + .Times(1); + + // ACT + int result = session.copy_and_write_async(); + + // ASSERT + ASSERT_EQ(result, -1); +} + + +TEST_F(VariableServerSession_test, copy_and_write_top_disabled) { + // ARRANGE + MockVariableServerSession session; + + // set disabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(false)); + + // Nothing else should be called + EXPECT_CALL(session, copy_sim_data()) + .Times(0); + + EXPECT_CALL(session, write_data()) + .Times(0); + + session.copy_and_write_top(100); +} + +TEST_F(VariableServerSession_test, copy_and_write_top_copy_top_write_async) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_TOP_OF_FRAME, VS_WRITE_ASYNC, false); + realtime_sync->active = true; + + EXPECT_CALL(session, get_frame_multiple()) + .WillRepeatedly(Return(10)); + EXPECT_CALL(session, get_frame_offset()) + .WillRepeatedly(Return(0)); + + // Should only copy + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_top(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_top_copy_top_write_when_copied) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_TOP_OF_FRAME, VS_WRITE_WHEN_COPIED, false); + realtime_sync->active = true; + + EXPECT_CALL(session, get_frame_multiple()) + .WillRepeatedly(Return(10)); + EXPECT_CALL(session, get_frame_offset()) + .WillRepeatedly(Return(0)); + + // Should only copy + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(1); + + // ACT + int result = session.copy_and_write_top(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_top_copy_top_dont_write_if_non_realtime) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_TOP_OF_FRAME, VS_WRITE_WHEN_COPIED, false); + realtime_sync->active = false; + + EXPECT_CALL(session, get_frame_multiple()) + .WillRepeatedly(Return(10)); + EXPECT_CALL(session, get_frame_offset()) + .WillRepeatedly(Return(0)); + + // Should only copy + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_top(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_top_copy_top_write_paused) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_TOP_OF_FRAME, VS_WRITE_WHEN_COPIED, true); + realtime_sync->active = true; + + EXPECT_CALL(session, get_frame_multiple()) + .WillRepeatedly(Return(10)); + EXPECT_CALL(session, get_frame_offset()) + .WillRepeatedly(Return(0)); + + // Should only copy + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_top(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + + +TEST_F(VariableServerSession_test, copy_and_write_top_wrong_offset) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + EXPECT_CALL(session, get_copy_mode()) + .WillRepeatedly(Return(VS_COPY_TOP_OF_FRAME)); + EXPECT_CALL(session, get_write_mode()) + .WillRepeatedly(Return(VS_WRITE_WHEN_COPIED)); + + realtime_sync->active = true; + + EXPECT_CALL(session, get_frame_multiple()) + .WillRepeatedly(Return(10)); + EXPECT_CALL(session, get_frame_offset()) + .WillRepeatedly(Return(0)); + + // Should only copy + EXPECT_CALL(session, copy_sim_data()) + .Times(0); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_top(101); + + // ASSERT + EXPECT_EQ(result, 0); +} + + + +TEST_F(VariableServerSession_test, copy_and_write_freeze_disabled) { + // ARRANGE + MockVariableServerSession session; + + // set disabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(false)); + + // Nothing else should be called + EXPECT_CALL(session, copy_sim_data()) + .Times(0); + + EXPECT_CALL(session, write_data()) + .Times(0); + + session.copy_and_write_freeze(100); +} + +TEST_F(VariableServerSession_test, copy_and_write_freeze_copy_top_write_async) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_TOP_OF_FRAME, VS_WRITE_ASYNC, false); + realtime_sync->active = true; + + EXPECT_CALL(session, get_freeze_frame_multiple()) + .WillRepeatedly(Return(10)); + EXPECT_CALL(session, get_freeze_frame_offset()) + .WillRepeatedly(Return(0)); + + // Should only copy + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_freeze(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_freeze_copy_top_write_when_copied) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_TOP_OF_FRAME, VS_WRITE_WHEN_COPIED, false); + realtime_sync->active = true; + + EXPECT_CALL(session, get_freeze_frame_multiple()) + .WillRepeatedly(Return(10)); + EXPECT_CALL(session, get_freeze_frame_offset()) + .WillRepeatedly(Return(0)); + + // Should only copy + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(1); + + // ACT + int result = session.copy_and_write_freeze(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_freeze_copy_top_dont_write_if_non_realtime) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_TOP_OF_FRAME, VS_WRITE_WHEN_COPIED, false); + realtime_sync->active = false; + + EXPECT_CALL(session, get_freeze_frame_multiple()) + .WillRepeatedly(Return(10)); + EXPECT_CALL(session, get_freeze_frame_offset()) + .WillRepeatedly(Return(0)); + + // Should only copy + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_freeze(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_freeze_copy_top_write_paused) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_TOP_OF_FRAME, VS_WRITE_WHEN_COPIED, true); + realtime_sync->active = true; + + EXPECT_CALL(session, get_freeze_frame_multiple()) + .WillRepeatedly(Return(10)); + EXPECT_CALL(session, get_freeze_frame_offset()) + .WillRepeatedly(Return(0)); + + // Should only copy + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_freeze(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + + +TEST_F(VariableServerSession_test, copy_and_write_freeze_wrong_offset) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + EXPECT_CALL(session, get_copy_mode()) + .WillRepeatedly(Return(VS_COPY_TOP_OF_FRAME)); + EXPECT_CALL(session, get_write_mode()) + .WillRepeatedly(Return(VS_WRITE_WHEN_COPIED)); + + realtime_sync->active = true; + + EXPECT_CALL(session, get_freeze_frame_multiple()) + .WillRepeatedly(Return(10)); + EXPECT_CALL(session, get_freeze_frame_offset()) + .WillRepeatedly(Return(0)); + + // Should only copy + EXPECT_CALL(session, copy_sim_data()) + .Times(0); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_freeze(101); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_scheduled_disabled) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(false)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(0); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_scheduled_wrong_mode) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(false)); + EXPECT_CALL(session, get_copy_mode()) + .WillRepeatedly(Return(VS_COPY_TOP_OF_FRAME)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(0); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_scheduled_wrong_tics) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(false)); + EXPECT_CALL(session, get_copy_mode()) + .WillRepeatedly(Return(VS_COPY_TOP_OF_FRAME)); + EXPECT_CALL(session, get_next_tics()) + .WillRepeatedly(Return(10)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(0); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + + +TEST_F(VariableServerSession_test, copy_and_write_scheduled_copy_and_write) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_SCHEDULED, VS_WRITE_WHEN_COPIED, false); + realtime_sync->active = true; + + EXPECT_CALL(session, get_next_tics()) + .WillRepeatedly(Return(100)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(1); + + // ACT + int result = session.copy_and_write_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_scheduled_copy_scheduled_write_async) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_SCHEDULED, VS_WRITE_ASYNC, false); + realtime_sync->active = true; + + EXPECT_CALL(session, get_next_tics()) + .WillRepeatedly(Return(100)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_scheduled_paused) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_SCHEDULED, VS_WRITE_ASYNC, true); + realtime_sync->active = true; + + EXPECT_CALL(session, get_next_tics()) + .WillRepeatedly(Return(100)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_scheduled_non_realtime) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_SCHEDULED, VS_WRITE_ASYNC, false); + realtime_sync->active = false; + + EXPECT_CALL(session, get_next_tics()) + .WillRepeatedly(Return(100)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + + +TEST_F(VariableServerSession_test, copy_and_write_scheduled_write_fails) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_SCHEDULED, VS_WRITE_WHEN_COPIED, false); + realtime_sync->active = true; + + EXPECT_CALL(session, get_next_tics()) + .WillRepeatedly(Return(100)); + + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .WillOnce(Return(-1)); + + EXPECT_CALL(session, set_exit_cmd()) + .Times(1); + + // ACT + int result = session.copy_and_write_scheduled(100); + + // ASSERT + EXPECT_EQ(result, -1); +} + + +TEST_F(VariableServerSession_test, copy_and_write_freeze_scheduled_disabled) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(false)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(0); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_freeze_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_freeze_scheduled_wrong_mode) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(false)); + EXPECT_CALL(session, get_copy_mode()) + .WillRepeatedly(Return(VS_COPY_TOP_OF_FRAME)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(0); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_freeze_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_freeze_scheduled_wrong_tics) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(false)); + EXPECT_CALL(session, get_copy_mode()) + .WillRepeatedly(Return(VS_COPY_TOP_OF_FRAME)); + EXPECT_CALL(session, get_freeze_next_tics()) + .WillRepeatedly(Return(10)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(0); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_freeze_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + + +TEST_F(VariableServerSession_test, copy_and_write_freeze_scheduled_copy_and_write) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_SCHEDULED, VS_WRITE_WHEN_COPIED, false); + realtime_sync->active = true; + + EXPECT_CALL(session, get_freeze_next_tics()) + .WillRepeatedly(Return(100)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(1); + + // ACT + int result = session.copy_and_write_freeze_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_freeze_scheduled_copy_scheduled_write_async) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_SCHEDULED, VS_WRITE_ASYNC, false); + realtime_sync->active = true; + + EXPECT_CALL(session, get_freeze_next_tics()) + .WillRepeatedly(Return(100)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_freeze_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_freeze_scheduled_paused) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_SCHEDULED, VS_WRITE_ASYNC, true); + realtime_sync->active = true; + + EXPECT_CALL(session, get_freeze_next_tics()) + .WillRepeatedly(Return(100)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_freeze_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(VariableServerSession_test, copy_and_write_freeze_scheduled_non_realtime) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_SCHEDULED, VS_WRITE_ASYNC, false); + realtime_sync->active = false; + + EXPECT_CALL(session, get_freeze_next_tics()) + .WillRepeatedly(Return(100)); + + // Shouldn't do anything + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .Times(0); + + // ACT + int result = session.copy_and_write_freeze_scheduled(100); + + // ASSERT + EXPECT_EQ(result, 0); +} + + +TEST_F(VariableServerSession_test, copy_and_write_freeze_scheduled_write_fails) { + // ARRANGE + MockVariableServerSession session; + + // set enabled + EXPECT_CALL(session, get_enabled()) + .WillOnce(Return(true)); + + set_session_modes(session, VS_COPY_SCHEDULED, VS_WRITE_WHEN_COPIED, false); + realtime_sync->active = true; + + EXPECT_CALL(session, get_freeze_next_tics()) + .WillRepeatedly(Return(100)); + + EXPECT_CALL(session, copy_sim_data()) + .Times(1); + + EXPECT_CALL(session, write_data()) + .WillOnce(Return(-1)); + + EXPECT_CALL(session, set_exit_cmd()) + .Times(1); + + // ACT + int result = session.copy_and_write_freeze_scheduled(100); + + // ASSERT + EXPECT_EQ(result, -1); +} \ No newline at end of file diff --git a/trick_source/sim_services/VariableServer/test/VariableServer_test.cc b/trick_source/sim_services/VariableServer/test/VariableServer_test.cc new file mode 100644 index 00000000..b3029c2c --- /dev/null +++ b/trick_source/sim_services/VariableServer/test/VariableServer_test.cc @@ -0,0 +1,112 @@ +/******************************TRICK HEADER************************************* +PURPOSE: ( Tests for the VariableServer class ) +*******************************************************************************/ + +#include +#include + +#include "MockVariableServerSession.hh" + +#include "trick/VariableServer.hh" + + +/* + Test Fixture. + */ +class VariableServer_test : public ::testing::Test { + protected: + Trick::VariableServer vs; + + VariableServer_test() { + + } + + ~VariableServer_test() { + } + + void SetUp() {} + void TearDown() {} +}; + +TEST_F(VariableServer_test, set_log_on) { + // ARRANGE + MockVariableServerSession session; + EXPECT_CALL(session, set_log_on()) + .Times(1); + + pthread_t id = (pthread_t) 5; + vs.add_session(id, &session); + + // ACT + vs.set_var_server_log_on(); + + // ASSERT + EXPECT_EQ(vs.get_log(), true); +} + +TEST_F(VariableServer_test, set_log_off) { + // ARRANGE + MockVariableServerSession session; + EXPECT_CALL(session, set_log_off()) + .Times(1); + + pthread_t id = (pthread_t) 5; + vs.add_session(id, &session); + + // ACT + vs.set_var_server_log_off(); + + // ASSERT + EXPECT_EQ(vs.get_log(), false); +} + +TEST_F(VariableServer_test, enabled_by_default) { + // ARRANGE + // ACT + // ASSERT + EXPECT_EQ(vs.get_enabled(), true); +} + +TEST_F(VariableServer_test, set_enabled) { + // ARRANGE + // ACT + vs.set_enabled(false); + + // ASSERT + EXPECT_EQ(vs.get_enabled(), false); +} + +TEST_F(VariableServer_test, info_msg_off_by_default) { + // ARRANGE + // ACT + // ASSERT + EXPECT_EQ(vs.get_info_msg(), false); +} + +TEST_F(VariableServer_test, set_info_msg) { + // ARRANGE + // ACT + vs.set_var_server_info_msg_on(); + + // ASSERT + EXPECT_EQ(vs.get_info_msg(), true); +} + +TEST_F(VariableServer_test, set_info_msg_off) { + // ARRANGE + // ACT + vs.set_var_server_info_msg_off(); + + // ASSERT + EXPECT_EQ(vs.get_info_msg(), false); +} + +TEST_F(VariableServer_test, info_dump) { + + MockVariableServerSession session; + pthread_t id = (pthread_t) 5; + vs.add_session(id, &session); + + std::cout << vs << std::endl; + +} \ No newline at end of file diff --git a/trick_source/sim_services/VariableServer/var_server_ext.cpp b/trick_source/sim_services/VariableServer/var_server_ext.cpp index a5d5d010..dd8fccba 100644 --- a/trick_source/sim_services/VariableServer/var_server_ext.cpp +++ b/trick_source/sim_services/VariableServer/var_server_ext.cpp @@ -11,9 +11,7 @@ extern Trick::VariableServer * the_vs ; -int command_debug = 0; - -Trick::VariableServerThread * get_vst() { +Trick::VariableServerSessionThread * get_vst() { return the_vs->get_vst(pthread_self()) ; } @@ -22,10 +20,6 @@ Trick::VariableServerSession * get_session() { } int var_add(std::string in_name) { - if (command_debug) { - std::cout << "var_add: " << in_name << std::endl; - } - Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -35,9 +29,6 @@ int var_add(std::string in_name) { } int var_add(std::string in_name, std::string in_units) { - if (command_debug) { - std::cout << "var_add: " << in_name << std::endl; - } Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -47,9 +38,6 @@ int var_add(std::string in_name, std::string in_units) { } int var_remove(std::string in_name) { - if (command_debug) { - std::cout << "var_remove: " << in_name << std::endl; - } Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -59,9 +47,6 @@ int var_remove(std::string in_name) { } int var_units(std::string var_name , std::string units_name) { - if (command_debug) { - std::cout << "var_units: " << var_name << std::endl; - } Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -71,9 +56,6 @@ int var_units(std::string var_name , std::string units_name) { } int var_exists(std::string in_name) { - if (command_debug) { - std::cout << "var_exists: " << in_name << std::endl; - } Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -83,9 +65,6 @@ int var_exists(std::string in_name) { } int var_send_once(std::string in_name) { - if (command_debug) { - std::cout << "var_send_once: " << in_name << std::endl; - } Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -95,9 +74,6 @@ int var_send_once(std::string in_name) { } int var_send_once(std::string in_name, int num) { - if (command_debug) { - std::cout << "var_send_once: " << in_name << std::endl; - } Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -107,10 +83,6 @@ int var_send_once(std::string in_name, int num) { } int var_send() { - if (command_debug) { - std::cout << "var_send: " << std::endl; - } - Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -120,9 +92,6 @@ int var_send() { } int var_clear() { - if (command_debug) { - std::cout << "var_clear: " << std::endl; - } Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -134,10 +103,6 @@ int var_clear() { } int var_cycle(double in_rate) { - if (command_debug) { - std::cout << "var_cycle: " << in_rate << std::endl; - } - Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -147,10 +112,6 @@ int var_cycle(double in_rate) { } int var_pause() { - if (command_debug) { - std::cout << "var_pause" << std::endl; - } - Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -161,9 +122,6 @@ int var_pause() { } int var_unpause() { - if (command_debug) { - std::cout << "var_unpause" << std::endl; - } Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -174,9 +132,6 @@ int var_unpause() { } int var_exit() { - if (command_debug) { - std::cout << "var_exit" << std::endl; - } Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -186,9 +141,6 @@ int var_exit() { } int var_validate_address(int on_off) { - if (command_debug) { - std::cout << "var_validate_address" << std::endl; - } Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -351,10 +303,7 @@ int var_write_stdio(int stream , std::string text ) { } int var_set_client_tag( std::string text ) { - if (command_debug) { - std::cout << "var_set_client_tag: " << text << std::endl; - } - Trick::VariableServerThread * vst = get_vst(); + Trick::VariableServerSessionThread * vst = get_vst(); if (vst != NULL) { vst->set_client_tag(text); @@ -372,9 +321,6 @@ int var_set_client_tag( std::string text ) { } int var_send_list_size() { - if (command_debug) { - std::cout << "var_send_list_size" << std::endl; - } Trick::VariableServerSession * session = get_session(); if (session != NULL ) { @@ -457,7 +403,6 @@ extern "C" void var_server_list_connections(void) { */ extern "C" const char * var_server_get_hostname(void) { const char * ret = (the_vs->get_hostname()) ; - printf("varserverext: %s", ret); return ret; } diff --git a/trick_source/trick_utils/connection_handlers/ClientConnection.cpp b/trick_source/trick_utils/connection_handlers/ClientConnection.cpp index f6945bc9..3df752b4 100644 --- a/trick_source/trick_utils/connection_handlers/ClientConnection.cpp +++ b/trick_source/trick_utils/connection_handlers/ClientConnection.cpp @@ -1,10 +1 @@ -#include "trick/ClientConnection.hh" - -std::string Trick::ClientConnection::getClientTag () { - return _client_tag; -} - -int Trick::ClientConnection::setClientTag (std::string tag) { - _client_tag = tag; - return 0; -} \ No newline at end of file +#include "trick/ClientConnection.hh" \ No newline at end of file diff --git a/trick_source/trick_utils/connection_handlers/MulticastGroup.cpp b/trick_source/trick_utils/connection_handlers/MulticastGroup.cpp new file mode 100644 index 00000000..35c9bc6d --- /dev/null +++ b/trick_source/trick_utils/connection_handlers/MulticastGroup.cpp @@ -0,0 +1,228 @@ +#include "trick/MulticastGroup.hh" +#include +#include +#include + + +Trick::MulticastGroup::MulticastGroup() : MulticastGroup(new SystemInterface) {} + +Trick::MulticastGroup::MulticastGroup (SystemInterface * system_interface) : _initialized(false), _system_interface(system_interface) {} + +Trick::MulticastGroup::~MulticastGroup() {} + +int Trick::MulticastGroup::restart () { + // Keep address list the same, but we may need to get a new socket + _system_interface = new SystemInterface(); + // return initialize(); + // return 0; +} + + +int Trick::MulticastGroup::broadcast (std::string message) { + if (!isInitialized()) { + return -1; + } + + const char * message_send = message.c_str(); + for (struct sockaddr_in& address : _addresses) { + int status = _system_interface->sendto(_socket , message_send , strlen(message_send) , 0 , (struct sockaddr *)&address , (socklen_t)sizeof(address)) ; + if (status == -1) { + char * readable_addr = inet_ntoa( address.sin_addr ); + std::string err_msg = "MulticastGroup: Failed to broadcast to address " + std::string(readable_addr); + perror(err_msg.c_str()); + } + } + return 0; +} + +int Trick::MulticastGroup::addAddress (std::string addr, int port) { + auto in_addr = _system_interface->inet_addr(addr.c_str()); + if (in_addr == -1) { + std::string error_msg = "MulticastGroup: Cannot add address " + addr + " to multicast group"; + perror(error_msg.c_str()); + return -1; + } + + struct sockaddr_in mcast_addr; + memset(&mcast_addr, 0, sizeof(mcast_addr)); + mcast_addr.sin_family = AF_INET; + mcast_addr.sin_addr.s_addr = in_addr; + mcast_addr.sin_port = htons((uint16_t) port);; + _addresses.emplace_back(mcast_addr); + return 0; +} + +bool Trick::MulticastGroup::isInitialized () { + return _initialized; +} + +int Trick::MulticastGroup::initialize() { + _socket = _system_interface->socket(AF_INET, SOCK_DGRAM, 0); + if (_socket < 0) { + perror("MulticastGroup: socket"); + return -1; + } + + int option_value = 1; + if (_system_interface->setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (char *) &option_value, (socklen_t) sizeof(option_value)) < 0) { + perror("MulticastGroup: setsockopt: reuseaddr"); + return -1; + } +#ifdef SO_REUSEPORT + if (_system_interface->setsockopt(_socket, SOL_SOCKET, SO_REUSEPORT, (char *) &option_value, sizeof(option_value)) < 0) { + perror("MulticastGroup: setsockopt: reuseport"); + return -1; + } +#endif + _initialized = true; + return 0; +} + +int Trick::MulticastGroup::initialize_with_receiving(std::string addr, std::string mcast_addr, int port) { + UDPConnection::initialize(addr, port); + + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = inet_addr(mcast_addr.c_str()); + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + int ret = setsockopt(_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) ; + if ( ret < 0 ) { + perror("ip_add_membership") ; + return ret ; + } + + // Save our info so that we know to ignore messages from ourself + memset(&_remote_serv_addr, 0, sizeof(struct sockaddr_in)); + _remote_serv_addr.sin_family = AF_INET; + _remote_serv_addr.sin_addr.s_addr = inet_addr(mcast_addr.c_str()); + _remote_serv_addr.sin_port = htons(port); + + int s_in_size = sizeof(_self_info) ; + getsockname( _socket , (struct sockaddr *)&_self_info, (socklen_t *)&s_in_size) ; + if ( _self_info.sin_addr.s_addr == 0 ) { + char hname[80]; + struct hostent *ip_host = NULL; + gethostname(hname, (size_t) 80); + ip_host = gethostbyname(hname); + if (ip_host != NULL) { + memcpy(&(_self_info.sin_addr.s_addr), ip_host->h_addr, (size_t) ip_host->h_length); + } else { + _self_info.sin_addr.s_addr = inet_addr(addr.c_str()); + _self_info.sin_port = htons(port); + } + } + + setBlockMode(false); + + + return 0; +} + +int Trick::MulticastGroup::disconnect() { + if (_initialized) { + _system_interface->close(_socket); + } + return 0; +} + + +int Trick::MulticastGroup::write (char * message, int size) { + if (!_started) + return -1; + + socklen_t sock_size = sizeof(_remote_serv_addr); + + return _system_interface->sendto(_socket, message, size, 0, (struct sockaddr *) &_remote_serv_addr, sock_size ); +} + +int Trick::MulticastGroup::write (const std::string& message) { + if (!_started) + return -1; + + char send_buf[message.length()+1]; + strcpy (send_buf, message.c_str()); + socklen_t sock_size = sizeof(_remote_serv_addr); + + return _system_interface->sendto(_socket, send_buf, message.length(), 0, (struct sockaddr *) &_remote_serv_addr, sock_size ); +} + + +int Trick::MulticastGroup::read (std::string& message, int max_len) { + if (!_started) + return 0; + + struct sockaddr_in s_in ; + socklen_t sock_size = sizeof(s_in) ; + + char incoming_msg[max_len]; + int nbytes = _system_interface->recvfrom( _socket, incoming_msg, MAX_CMD_LEN, MSG_PEEK,(struct sockaddr *)&s_in, &sock_size ) ; + + if (nbytes == 0 ) { + return 0; + } + + if (nbytes == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // There's nothing ready in the socket, just return an empty string + message = ""; + return 0; + } + + // Otherwise, some other system error has occurred. Return an error. + return -1; + } + + // Make sure that this message isn't from ourself + if ( s_in.sin_addr.s_addr == _self_info.sin_addr.s_addr && s_in.sin_port == _self_info.sin_port) { + // Clear out the socket + _system_interface->recvfrom( _socket, incoming_msg, MAX_CMD_LEN, 0 , 0, 0) ; + return 0; + } + + /* find the last newline that is present on the socket */ + incoming_msg[nbytes] = '\0' ; + char *last_newline = rindex( incoming_msg , '\n') ; + + /* if there is a newline then there is a complete command on the socket */ + if ( last_newline != NULL ) { + socklen_t sock_size = sizeof(_remote_serv_addr); + /* only remove up to (and including) the last newline on the socket */ + int size = last_newline - incoming_msg + 1; + nbytes = _system_interface->recvfrom( _socket, incoming_msg, size, 0 , 0, 0) ; + } else { + nbytes = 0 ; + } + + std::stringstream msg_stream; + + if ( nbytes > 0 ) { + // Strip out \r characers + + int msg_len = nbytes ; + incoming_msg[msg_len] = '\0' ; + + for( int ii = 0 , jj = 0 ; ii <= msg_len ; ii++ ) { + if ( incoming_msg[ii] != '\r' ) { + msg_stream << incoming_msg[ii] ; + } + } + } + + message = msg_stream.str(); + return message.size(); +} + +std::string Trick::MulticastGroup::getClientHostname() { + if (!_started) { + return ""; + } + + return inet_ntoa(_remote_serv_addr.sin_addr); +} + +int Trick::MulticastGroup::getClientPort() { + if (!_started) { + return 0; + } + + return ntohs(_remote_serv_addr.sin_port); +} \ No newline at end of file diff --git a/trick_source/trick_utils/connection_handlers/MulticastManager.cpp b/trick_source/trick_utils/connection_handlers/MulticastManager.cpp deleted file mode 100644 index f5b82a31..00000000 --- a/trick_source/trick_utils/connection_handlers/MulticastManager.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "trick/MulticastManager.hh" -#include -#include - -Trick::MulticastManager::MulticastManager() { -} - -Trick::MulticastManager::~MulticastManager() { - -} - -int Trick::MulticastManager::restart () { - // Keep address list the same, but we may need to get a new socket - return initialize(); -} - - -int Trick::MulticastManager::broadcast (std::string message) { - if (!is_initialized()) { - initialize(); - } - const char * message_send = message.c_str(); - for (struct sockaddr_in& address : addresses) { - sendto(mcast_socket , message_send , strlen(message_send) , 0 , (struct sockaddr *)&address , (socklen_t)sizeof(address)) ; - } - return 0; -} - -int Trick::MulticastManager::addAddress (std::string addr, int port) { - - struct sockaddr_in mcast_addr; - memset(&mcast_addr, 0, sizeof(mcast_addr)); - mcast_addr.sin_family = AF_INET; - mcast_addr.sin_addr.s_addr = inet_addr(addr.c_str()); - mcast_addr.sin_port = htons((uint16_t) port); - addresses.emplace_back(mcast_addr); - return 0; -} - -int Trick::MulticastManager::is_initialized () { - return initialized; -} - -int Trick::MulticastManager::initialize() { - - mcast_socket = socket(AF_INET, SOCK_DGRAM, 0); - if (mcast_socket < 0) { - perror("vs_mcast_init socket"); - return -1; - } - - int option_value = 1; - if (setsockopt(mcast_socket, SOL_SOCKET, SO_REUSEADDR, (char *) &option_value, (socklen_t) sizeof(option_value)) < 0) { - perror("setsockopt: reuseaddr"); - return -1; - } -#ifdef SO_REUSEPORT - if (setsockopt(mcast_socket, SOL_SOCKET, SO_REUSEPORT, (char *) &option_value, sizeof(option_value)) < 0) { - perror("setsockopt: reuseport"); - return -1; - } -#endif - initialized = 1; - return 0; -} \ No newline at end of file diff --git a/trick_source/trick_utils/connection_handlers/ClientListener.cpp b/trick_source/trick_utils/connection_handlers/TCPClientListener.cpp similarity index 70% rename from trick_source/trick_utils/connection_handlers/ClientListener.cpp rename to trick_source/trick_utils/connection_handlers/TCPClientListener.cpp index 9d7cfc96..12b5e158 100644 --- a/trick_source/trick_utils/connection_handlers/ClientListener.cpp +++ b/trick_source/trick_utils/connection_handlers/TCPClientListener.cpp @@ -9,12 +9,12 @@ #include #include -#include "trick/ClientListener.hh" +#include "trick/TCPClientListener.hh" -Trick::ClientListener::ClientListener () : ClientListener (new SystemInterface()) {} -Trick::ClientListener::ClientListener (SystemInterface * system_interface) : _listen_socket(-1), _hostname(""), _port(0), _client_tag(""), _initialized(false), _system_interface(system_interface) {} +Trick::TCPClientListener::TCPClientListener () : TCPClientListener (new SystemInterface()) {} +Trick::TCPClientListener::TCPClientListener (SystemInterface * system_interface) : _listen_socket(-1), _hostname(""), _port(0), _client_tag(""), _initialized(false), _system_interface(system_interface) {} -Trick::ClientListener::~ClientListener () { +Trick::TCPClientListener::~TCPClientListener () { // Clean up our socket if initialized if (_initialized) { close (_listen_socket); @@ -23,7 +23,7 @@ Trick::ClientListener::~ClientListener () { delete _system_interface; } -int Trick::ClientListener::initialize(std::string in_hostname, int in_port) { +int Trick::TCPClientListener::initialize(std::string in_hostname, int in_port) { if ((_listen_socket = _system_interface->socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror ("Server: Unable to open socket"); @@ -114,12 +114,12 @@ int Trick::ClientListener::initialize(std::string in_hostname, int in_port) { return 0; } -int Trick::ClientListener::initialize() { +int Trick::TCPClientListener::initialize() { return initialize("", 0); } -int Trick::ClientListener::setBlockMode(bool blocking) { +int Trick::TCPClientListener::setBlockMode(bool blocking) { if (!_initialized) return -1; @@ -147,7 +147,7 @@ int Trick::ClientListener::setBlockMode(bool blocking) { } -bool Trick::ClientListener::checkForNewConnections() { +bool Trick::TCPClientListener::checkForNewConnections() { if (!_initialized) return false; @@ -170,7 +170,7 @@ bool Trick::ClientListener::checkForNewConnections() { -std::string Trick::ClientListener::getHostname () { +std::string Trick::TCPClientListener::getHostname () { if (!_initialized) return ""; @@ -178,7 +178,7 @@ std::string Trick::ClientListener::getHostname () { } -int Trick::ClientListener::getPort() { +int Trick::TCPClientListener::getPort() { if (!_initialized) return -1; @@ -186,7 +186,7 @@ int Trick::ClientListener::getPort() { } -int Trick::ClientListener::disconnect() { +int Trick::TCPClientListener::disconnect() { if (!_initialized) { return -1; } @@ -198,23 +198,48 @@ int Trick::ClientListener::disconnect() { return 0; } -bool Trick::ClientListener::validateSourceAddress(std::string requested_source_address) { - struct addrinfo hints, *res; - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = 0; +bool Trick::TCPClientListener::validateSourceAddress(std::string in_hostname) { + // struct addrinfo hints, *res; + // memset(&hints, 0, sizeof hints); + // hints.ai_family = AF_INET; + // hints.ai_socktype = SOCK_STREAM; + // hints.ai_protocol = 0; - int err; - if ((err = _system_interface->getaddrinfo(requested_source_address.c_str(), 0, &hints, &res)) != 0) { - std::cerr << "Unable to lookup address: " << gai_strerror(err) << std::endl; + // int err; + // if ((err = _system_interface->getaddrinfo(requested_source_address.c_str(), 0, &hints, &res)) != 0) { + // std::cerr << "Unable to lookup address: " << gai_strerror(err) << std::endl; + // return false; + // } + + // Look up the hostname + char name[80]; + gethostname(name, (size_t) 80); + + struct hostent *ip_host ; + sockaddr_in s_in; + socklen_t s_in_size = sizeof(s_in) ; + + s_in.sin_family = AF_INET; + + if (in_hostname == "" || in_hostname == "localhost" || strcmp(in_hostname.c_str(),name) == 0) { + s_in.sin_addr.s_addr = INADDR_ANY; + _hostname = std::string(name); + } else if ( inet_pton(AF_INET, in_hostname.c_str(), (struct in_addr *)&s_in.sin_addr.s_addr) == 1 ) { + /* numeric character string address */ + _hostname = in_hostname; + } else if ( (ip_host = gethostbyname(in_hostname.c_str())) != NULL ) { + /* some name other than the default name was given */ + memcpy((void *) &(s_in.sin_addr.s_addr), (const void *) ip_host->h_addr, (size_t) ip_host->h_length); + _hostname = in_hostname; + } else { + perror("Server: Could not determine source address"); return false; } return true; } -int Trick::ClientListener::checkSocket() { +int Trick::TCPClientListener::checkSocket() { if (!_initialized) return -1; @@ -226,11 +251,11 @@ int Trick::ClientListener::checkSocket() { return 0; } -bool Trick::ClientListener::isInitialized() { +bool Trick::TCPClientListener::isInitialized() { return _initialized; } -Trick::TCPConnection * Trick::ClientListener::setUpNewConnection () { +Trick::TCPConnection * Trick::TCPClientListener::setUpNewConnection () { if (!_initialized) return NULL; @@ -238,8 +263,9 @@ Trick::TCPConnection * Trick::ClientListener::setUpNewConnection () { return connection; } -int Trick::ClientListener::restart () { +int Trick::TCPClientListener::restart () { _system_interface = new SystemInterface(); + return 0; } diff --git a/trick_source/trick_utils/connection_handlers/TCPConnection.cpp b/trick_source/trick_utils/connection_handlers/TCPConnection.cpp index 604c9976..a72ef7f4 100644 --- a/trick_source/trick_utils/connection_handlers/TCPConnection.cpp +++ b/trick_source/trick_utils/connection_handlers/TCPConnection.cpp @@ -5,6 +5,8 @@ #include #include +Trick::TCPConnection::TCPConnection () : TCPConnection(0, new SystemInterface()) {} + Trick::TCPConnection::TCPConnection (SystemInterface * system_interface) : TCPConnection(0, system_interface) {} Trick::TCPConnection::TCPConnection (int listen_socket) : TCPConnection(listen_socket, new SystemInterface()) {} @@ -53,10 +55,11 @@ int Trick::TCPConnection::write (const std::string& message) { return _system_interface->send(_socket, send_buf, message.length(), 0); } -std::string Trick::TCPConnection::read (int max_len) { +int Trick::TCPConnection::read (std::string& message, int max_len) { if (!_connected) { std::cerr << "Trying to read from a socket that is not connected" << std::endl; - return ""; + message = ""; + return 0; } char incoming_msg[max_len]; @@ -65,16 +68,19 @@ std::string Trick::TCPConnection::read (int max_len) { int nbytes = _system_interface->recv(_socket, incoming_msg, max_receive_length, MSG_PEEK); if (nbytes == 0 ) { - return std::string(""); + message = ""; + return 0; } if (nbytes == -1) { if (errno == EAGAIN) { - return std::string(""); + message = ""; + return 0; } else { std::string error_msg = "Error while reading from socket " + std::to_string(_socket); perror(error_msg.c_str()); - return std::string(""); + message = ""; + return -1; } } @@ -105,7 +111,8 @@ std::string Trick::TCPConnection::read (int max_len) { } } - return msg_stream.str(); + message = msg_stream.str(); + return message.size(); } @@ -126,7 +133,7 @@ int Trick::TCPConnection::setBlockMode(bool blocking) { int flag = _system_interface->fcntl(_socket, F_GETFL, 0); if (flag == -1) { - std::string error_message = "Unable to get flags for fd " + std::to_string(_socket) + " block mode to " + std::to_string(blocking); + std::string error_message = "Unable to get flags for fd " + std::to_string(_socket); perror (error_message.c_str()); return -1; } @@ -153,4 +160,42 @@ bool Trick::TCPConnection::isInitialized() { int Trick::TCPConnection::restart() { _system_interface = new SystemInterface(); + return 0; +} + +std::string Trick::TCPConnection::getClientTag () { + return _client_tag; +} + +int Trick::TCPConnection::setClientTag (std::string tag) { + _client_tag = tag; + return 0; +} + +std::string Trick::TCPConnection::getClientHostname() { + if (!_connected) { + return ""; + } + + struct sockaddr_in otherside; + socklen_t len = (socklen_t)sizeof(otherside); + + if (getpeername(_socket, (struct sockaddr*)&otherside, &len) != 0) + return ""; + + return inet_ntoa(otherside.sin_addr); +} + +int Trick::TCPConnection::getClientPort() { + if (!_connected) { + return 0; + } + + struct sockaddr_in otherside; + socklen_t len = (socklen_t)sizeof(otherside); + + if (getpeername(_socket, (struct sockaddr*)&otherside, &len) != 0) + return 0; + + return ntohs(otherside.sin_port); } \ No newline at end of file diff --git a/trick_source/trick_utils/connection_handlers/UDPConnection.cpp b/trick_source/trick_utils/connection_handlers/UDPConnection.cpp index d8be0276..e9076977 100644 --- a/trick_source/trick_utils/connection_handlers/UDPConnection.cpp +++ b/trick_source/trick_utils/connection_handlers/UDPConnection.cpp @@ -117,9 +117,9 @@ int Trick::UDPConnection::write (const std::string& message) { return _system_interface->sendto(_socket, send_buf, message.length(), 0, (struct sockaddr *) &_remote_serv_addr, sock_size ); } -std::string Trick::UDPConnection::read (int max_len) { +int Trick::UDPConnection::read (std::string& message, int max_len) { if (!_started) - return ""; + return 0; char incoming_msg[max_len]; int nbytes = _system_interface->recvfrom( _socket, incoming_msg, MAX_CMD_LEN, MSG_PEEK, NULL, NULL ) ; @@ -127,26 +127,36 @@ std::string Trick::UDPConnection::read (int max_len) { return 0; } - if (nbytes != -1) { // -1 means socket is nonblocking and no data to read - /* find the last newline that is present on the socket */ - incoming_msg[nbytes] = '\0' ; - char *last_newline = rindex( incoming_msg , '\n') ; - - /* if there is a newline then there is a complete command on the socket */ - if ( last_newline != NULL ) { - socklen_t sock_size = sizeof(_remote_serv_addr); - /* Save the remote host information so we know where to send replies */ - /* only remove up to (and including) the last newline on the socket */ - int size = last_newline - incoming_msg + 1; - nbytes = _system_interface->recvfrom( _socket, incoming_msg, size, 0 , (struct sockaddr *) &_remote_serv_addr, &sock_size ) ; - } else { - nbytes = 0 ; + if (nbytes == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // There's nothing ready in the socket, just return an empty string + message = ""; + return 0; } + + // Otherwise, some other system error has occurred. Return an error. + return -1; } + /* find the last newline that is present on the socket */ + incoming_msg[nbytes] = '\0' ; + char *last_newline = rindex( incoming_msg , '\n') ; + + /* if there is a newline then there is a complete command on the socket */ + if ( last_newline != NULL ) { + socklen_t sock_size = sizeof(_remote_serv_addr); + /* Save the remote host information so we know where to send replies */ + /* only remove up to (and including) the last newline on the socket */ + int size = last_newline - incoming_msg + 1; + nbytes = _system_interface->recvfrom( _socket, incoming_msg, size, 0 , (struct sockaddr *) &_remote_serv_addr, &sock_size ) ; + } else { + nbytes = 0 ; + } + std::stringstream msg_stream; if ( nbytes > 0 ) { + // Strip out \r characers int msg_len = nbytes ; incoming_msg[msg_len] = '\0' ; @@ -158,7 +168,8 @@ std::string Trick::UDPConnection::read (int max_len) { } } - return msg_stream.str(); + message = msg_stream.str(); + return message.size(); } int Trick::UDPConnection::disconnect () { @@ -178,7 +189,7 @@ int Trick::UDPConnection::setBlockMode(bool blocking) { int flag = _system_interface->fcntl(_socket, F_GETFL, 0); if (flag == -1) { - std::string error_message = "Unable to get flags for fd " + std::to_string(_socket) + " block mode to " + std::to_string(blocking); + std::string error_message = "Unable to get flags for fd " + std::to_string(_socket); perror (error_message.c_str()); return -1; } @@ -200,6 +211,7 @@ int Trick::UDPConnection::setBlockMode(bool blocking) { int Trick::UDPConnection::restart() { _system_interface = new SystemInterface(); + return 0; } bool Trick::UDPConnection::isInitialized() { @@ -212,4 +224,29 @@ int Trick::UDPConnection::getPort() { std::string Trick::UDPConnection::getHostname() { return _hostname; +} + +std::string Trick::UDPConnection::getClientTag () { + return _client_tag; +} + +int Trick::UDPConnection::setClientTag (std::string tag) { + _client_tag = tag; + return 0; +} + +std::string Trick::UDPConnection::getClientHostname() { + if (!_initialized) { + return ""; + } + + return inet_ntoa(_remote_serv_addr.sin_addr); +} + +int Trick::UDPConnection::getClientPort() { + if (!_initialized) { + return 0; + } + + return ntohs(_remote_serv_addr.sin_port); } \ No newline at end of file diff --git a/trick_source/trick_utils/connection_handlers/test/MulticastGroup_test.cpp b/trick_source/trick_utils/connection_handlers/test/MulticastGroup_test.cpp new file mode 100644 index 00000000..a664f9c9 --- /dev/null +++ b/trick_source/trick_utils/connection_handlers/test/MulticastGroup_test.cpp @@ -0,0 +1,136 @@ +#include + +#include "trick/MulticastGroup.hh" +#include "SystemInterfaceMock/SystemInterfaceMock.hh" + +class MulticastGroupTest : public testing::Test { + + protected: + MulticastGroupTest() : system_context(new SystemInferfaceMock()), mcast (system_context) {} + ~MulticastGroupTest(){ + mcast.disconnect(); + } + + SystemInferfaceMock * system_context; + Trick::MulticastGroup mcast; +}; + +TEST_F(MulticastGroupTest, not_initialized) { + // ARRANGE + // ACT + // ASSERT + EXPECT_EQ(mcast.isInitialized(), 0); +} + +TEST_F(MulticastGroupTest, initialize) { + // ARRANGE + // ACT + mcast.initialize(); + // ASSERT + EXPECT_EQ(mcast.isInitialized(), 1); +} + +TEST_F(MulticastGroupTest, initialize_socket_fails) { + // ARRANGE + system_context->register_socket_impl([](int a, int b, int c) { + errno = EPERM; + return -1; + }); + + // ACT + mcast.initialize(); + // ASSERT + EXPECT_EQ(mcast.isInitialized(), 0); +} + +TEST_F(MulticastGroupTest, initialize_sockopt_reuseaddr_fails) { + // ARRANGE + system_context->register_setsockopt_impl([](int sockfd, int level, int optname, const void *optval, socklen_t optlen) { + if (optname == SO_REUSEADDR) { + errno = EINVAL; + return -1; + } + return 0; + }); + + // ACT + mcast.initialize(); + // ASSERT + EXPECT_EQ(mcast.isInitialized(), 0); +} + +TEST_F(MulticastGroupTest, initialize_sockopt_reuseport_fails) { + // ARRANGE + system_context->register_setsockopt_impl([](int sockfd, int level, int optname, const void *optval, socklen_t optlen) { + if (optname == SO_REUSEPORT) { + errno = EINVAL; + return -1; + } + return 0; + }); + + // ACT + mcast.initialize(); + // ASSERT + EXPECT_EQ(mcast.isInitialized(), 0); +} + +TEST_F(MulticastGroupTest, add_address) { + // ARRANGE + // ACT + int result = mcast.addAddress("239.3.14.15", 9265); + + // ASSERT + EXPECT_EQ(result, 0); +} + +TEST_F(MulticastGroupTest, add_address_fails) { + // ARRANGE + system_context->register_inet_addr_impl([](const char * addr) { + return -1; + }); + + // ACT + int result = mcast.addAddress("239.3.14.15", 9265); + + // ASSERT + EXPECT_EQ(result, -1); +} + +TEST_F(MulticastGroupTest, broadcast_uninitialized) { + // ARRANGE + // ACT + int result = mcast.broadcast("Some message"); + + // ASSERT + EXPECT_EQ(result, -1); +} + +TEST_F(MulticastGroupTest, broadcast) { + // ARRANGE + mcast.initialize(); + mcast.addAddress("239.3.14.15", 9265); + + // ACT + int result = mcast.broadcast("Some message"); + + // ASSERT + EXPECT_EQ(result, 0); +} + + +TEST_F(MulticastGroupTest, broadcast_send_fails) { + // ARRANGE + system_context->register_sendto_impl([](int socket, const void * buffer, size_t length, int flags, const struct sockaddr * dest_addr, socklen_t dest_len) { + return -1; + }); + + mcast.initialize(); + mcast.addAddress("239.3.14.15", 9265); + + // ACT + int result = mcast.broadcast("Some message"); + + // ASSERT + EXPECT_EQ(result, 0); +} \ No newline at end of file diff --git a/trick_source/trick_utils/connection_handlers/test/SystemInterfaceMock/SystemInterfaceMock.hh b/trick_source/trick_utils/connection_handlers/test/SystemInterfaceMock/SystemInterfaceMock.hh index 2463582c..5d3b8f6f 100644 --- a/trick_source/trick_utils/connection_handlers/test/SystemInterfaceMock/SystemInterfaceMock.hh +++ b/trick_source/trick_utils/connection_handlers/test/SystemInterfaceMock/SystemInterfaceMock.hh @@ -37,6 +37,8 @@ typedef std::function recv_func_type; typedef std::function recvfrom_func_type; +typedef std::function inet_addr_func_type; + class SystemInferfaceMock : public SystemInterface { public: @@ -57,6 +59,7 @@ class SystemInferfaceMock : public SystemInterface { real_sendto_impl(); real_recv_impl(); real_recvfrom_impl(); + real_inet_addr_impl(); } void set_all_real () { @@ -76,6 +79,7 @@ class SystemInferfaceMock : public SystemInterface { real_sendto_impl(); real_recv_impl(); real_recvfrom_impl(); + real_inet_addr_impl(); } void set_all_noop() { @@ -95,6 +99,7 @@ class SystemInferfaceMock : public SystemInterface { noop_sendto_impl(); noop_recv_impl(); noop_recvfrom_impl(); + noop_inet_addr_impl(); } @@ -118,7 +123,7 @@ class SystemInferfaceMock : public SystemInterface { // bind implementation public: - virtual int bind (int socket, struct sockaddr * address, socklen_t address_len) override { return bind_impl( socket, address, address_len); } + virtual int bind (int socket, const struct sockaddr * address, socklen_t address_len) override { return bind_impl( socket, address, address_len); } void register_bind_impl (bind_func_type impl) { bind_impl = impl; } void real_bind_impl () { bind_impl = [](int socket, const struct sockaddr * address, socklen_t address_len) -> int { return ::bind( socket, address, address_len); }; } void noop_bind_impl () { bind_impl = [](int socket, const struct sockaddr * address, socklen_t address_len) -> int { return 0; }; } @@ -233,6 +238,15 @@ class SystemInferfaceMock : public SystemInterface { private: recvfrom_func_type recvfrom_impl; + // inet_addr implementation + public: + virtual in_addr_t inet_addr (const char * cp) override { return inet_addr_impl( cp); } + void register_inet_addr_impl (inet_addr_func_type impl) { inet_addr_impl = impl; } + void real_inet_addr_impl () { inet_addr_impl = [](const char * cp) -> in_addr_t { return ::inet_addr( cp); }; } + void noop_inet_addr_impl () { inet_addr_impl = [](const char * cp) -> in_addr_t { return 0; }; } + private: + inet_addr_func_type inet_addr_impl; + }; #endif diff --git a/trick_source/trick_utils/connection_handlers/test/ClientListener_test.cpp b/trick_source/trick_utils/connection_handlers/test/TCPClientListener_test.cpp similarity index 79% rename from trick_source/trick_utils/connection_handlers/test/ClientListener_test.cpp rename to trick_source/trick_utils/connection_handlers/test/TCPClientListener_test.cpp index 02f9f125..0a4bf7a4 100644 --- a/trick_source/trick_utils/connection_handlers/test/ClientListener_test.cpp +++ b/trick_source/trick_utils/connection_handlers/test/TCPClientListener_test.cpp @@ -12,26 +12,26 @@ #include #include -#include "trick/ClientListener.hh" +#include "trick/TCPClientListener.hh" #include "SystemInterfaceMock/SystemInterfaceMock.hh" -class ClientListenerTest : public testing::Test { +class TCPClientListenerTest : public testing::Test { protected: - ClientListenerTest() : system_context(new SystemInferfaceMock()), listener (system_context) {} - ~ClientListenerTest(){} + TCPClientListenerTest() : system_context(new SystemInferfaceMock()), listener (system_context) {} + ~TCPClientListenerTest(){} SystemInferfaceMock * system_context; - Trick::ClientListener listener; + Trick::TCPClientListener listener; }; -TEST_F( ClientListenerTest, initialized ) { +TEST_F( TCPClientListenerTest, initialized ) { EXPECT_EQ(listener.isInitialized(), false); } -TEST_F( ClientListenerTest, initialize_localhost_0 ) { +TEST_F( TCPClientListenerTest, initialize_localhost_0 ) { // ARRANGE // Look up the hostname char name[80]; @@ -45,7 +45,7 @@ TEST_F( ClientListenerTest, initialize_localhost_0 ) { EXPECT_EQ(listener.getHostname(), std::string(name)); } -TEST_F( ClientListenerTest, initialize_localhost_54321 ) { +TEST_F( TCPClientListenerTest, initialize_localhost_54321 ) { // ARRANGE // ACT listener.initialize("localhost", 54321); @@ -55,7 +55,7 @@ TEST_F( ClientListenerTest, initialize_localhost_54321 ) { EXPECT_EQ(listener.getPort(), 54321); } -TEST_F( ClientListenerTest, initialize_no_args ) { +TEST_F( TCPClientListenerTest, initialize_no_args ) { // ARRANGE // ACT listener.initialize(); @@ -65,7 +65,7 @@ TEST_F( ClientListenerTest, initialize_no_args ) { EXPECT_GT(listener.getPort(), 1000); } -TEST_F( ClientListenerTest, initialize_localhost_numerical_54321 ) { +TEST_F( TCPClientListenerTest, initialize_localhost_numerical_54321 ) { // ARRANGE // ACT listener.initialize("127.0.0.1", 54321); @@ -75,7 +75,7 @@ TEST_F( ClientListenerTest, initialize_localhost_numerical_54321 ) { EXPECT_EQ(listener.getPort(), 54321); } -TEST_F( ClientListenerTest, initialize_invalid_hostname ) { +TEST_F( TCPClientListenerTest, initialize_invalid_hostname ) { // ARRANGE // ACT listener.initialize("some_invalid_hostname", 0); @@ -84,7 +84,7 @@ TEST_F( ClientListenerTest, initialize_invalid_hostname ) { EXPECT_EQ(listener.isInitialized(), false); } -TEST_F( ClientListenerTest, failed_socket ) { +TEST_F( TCPClientListenerTest, failed_socket ) { // ARRANGE system_context->register_socket_impl([](int a, int b, int c) { errno = EPERM; @@ -98,7 +98,7 @@ TEST_F( ClientListenerTest, failed_socket ) { EXPECT_EQ(listener.isInitialized(), false); } -TEST_F( ClientListenerTest, failed_setsockopt_reuseaddr ) { +TEST_F( TCPClientListenerTest, failed_setsockopt_reuseaddr ) { // ARRANGE system_context->register_setsockopt_impl([](int sockfd, int level, int optname, const void *optval, socklen_t optlen) { errno = EINVAL; @@ -112,7 +112,7 @@ TEST_F( ClientListenerTest, failed_setsockopt_reuseaddr ) { EXPECT_EQ(listener.isInitialized(), false); } -TEST_F( ClientListenerTest, failed_setsockopt_buffering ) { +TEST_F( TCPClientListenerTest, failed_setsockopt_buffering ) { // ARRANGE system_context->register_setsockopt_impl([](int sockfd, int level, int optname, const void *optval, socklen_t optlen) { if (level == IPPROTO_TCP && optname == TCP_NODELAY) { @@ -130,7 +130,7 @@ TEST_F( ClientListenerTest, failed_setsockopt_buffering ) { EXPECT_EQ(listener.isInitialized(), false); } -TEST_F( ClientListenerTest, failed_bind ) { +TEST_F( TCPClientListenerTest, failed_bind ) { // ARRANGE system_context->register_bind_impl([](int sockfd, const struct sockaddr *addr,socklen_t addrlen) { errno = EADDRINUSE; @@ -144,7 +144,7 @@ TEST_F( ClientListenerTest, failed_bind ) { EXPECT_EQ(listener.isInitialized(), false); } -TEST_F( ClientListenerTest, failed_sockname ) { +TEST_F( TCPClientListenerTest, failed_sockname ) { // ARRANGE system_context->register_getsockname_impl([](int sockfd, struct sockaddr *addr, socklen_t *addrlen) { ((struct sockaddr_in *) addr)->sin_port = htons(1234); @@ -158,7 +158,7 @@ TEST_F( ClientListenerTest, failed_sockname ) { EXPECT_EQ(listener.isInitialized(), false); } -TEST_F( ClientListenerTest, failed_listen ) { +TEST_F( TCPClientListenerTest, failed_listen ) { // ARRANGE system_context->register_listen_impl([](int sockfd, int backlog) { errno = EADDRINUSE; @@ -172,7 +172,7 @@ TEST_F( ClientListenerTest, failed_listen ) { EXPECT_EQ(listener.isInitialized(), false); } -TEST_F( ClientListenerTest, checkForNewConnections_uninitialized ) { +TEST_F( TCPClientListenerTest, checkForNewConnections_uninitialized ) { // ARRANGE // ACT bool result = listener.checkForNewConnections(); @@ -181,7 +181,7 @@ TEST_F( ClientListenerTest, checkForNewConnections_uninitialized ) { EXPECT_EQ(result, false); } -TEST_F( ClientListenerTest, checkForNewConnections ) { +TEST_F( TCPClientListenerTest, checkForNewConnections ) { // ARRANGE int socket_fd; system_context->register_socket_impl([&socket_fd](int a, int b, int c) { @@ -201,7 +201,7 @@ TEST_F( ClientListenerTest, checkForNewConnections ) { EXPECT_EQ(result, true); } -TEST_F( ClientListenerTest, checkForNewConnections_select_error ) { +TEST_F( TCPClientListenerTest, checkForNewConnections_select_error ) { // ARRANGE system_context->register_select_impl([](int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout) { return -1; @@ -215,7 +215,7 @@ TEST_F( ClientListenerTest, checkForNewConnections_select_error ) { EXPECT_EQ(result, false); } -TEST_F( ClientListenerTest, setBlockMode_false ) { +TEST_F( TCPClientListenerTest, setBlockMode_false ) { // ARRANGE int socket_fd; system_context->register_socket_impl([&socket_fd](int a, int b, int c) { @@ -234,7 +234,7 @@ TEST_F( ClientListenerTest, setBlockMode_false ) { EXPECT_TRUE(flag & O_NONBLOCK); } -TEST_F( ClientListenerTest, setBlockMode_nonblocking) { +TEST_F( TCPClientListenerTest, setBlockMode_nonblocking) { // ARRANGE int socket_fd; system_context->register_socket_impl([&socket_fd](int a, int b, int c) { @@ -254,7 +254,7 @@ TEST_F( ClientListenerTest, setBlockMode_nonblocking) { } -TEST_F( ClientListenerTest, setBlockMode_blocking) { +TEST_F( TCPClientListenerTest, setBlockMode_blocking) { // ARRANGE int socket_fd; system_context->register_socket_impl([&socket_fd](int a, int b, int c) { @@ -273,7 +273,7 @@ TEST_F( ClientListenerTest, setBlockMode_blocking) { EXPECT_FALSE(flag & O_NONBLOCK); } -TEST_F( ClientListenerTest, setBlockMode_fcntl_getfl_fail) { +TEST_F( TCPClientListenerTest, setBlockMode_fcntl_getfl_fail) { // ARRANGE int socket_fd; system_context->register_socket_impl([&socket_fd](int a, int b, int c) { @@ -293,7 +293,7 @@ TEST_F( ClientListenerTest, setBlockMode_fcntl_getfl_fail) { EXPECT_EQ(status, -1); } -TEST_F( ClientListenerTest, setBlockMode_fcntl_setfl_fail) { +TEST_F( TCPClientListenerTest, setBlockMode_fcntl_setfl_fail) { // ARRANGE int socket_fd; system_context->register_socket_impl([&socket_fd](int a, int b, int c) { @@ -318,7 +318,7 @@ TEST_F( ClientListenerTest, setBlockMode_fcntl_setfl_fail) { } -TEST_F( ClientListenerTest, validateSourceAddress_localhost) { +TEST_F( TCPClientListenerTest, validateSourceAddress_localhost) { // ARRANGE // ACT bool status = listener.validateSourceAddress("localhost"); @@ -327,7 +327,7 @@ TEST_F( ClientListenerTest, validateSourceAddress_localhost) { EXPECT_EQ(status, true); } -TEST_F( ClientListenerTest, validateSourceAddress_junk) { +TEST_F( TCPClientListenerTest, validateSourceAddress_junk) { // ARRANGE // ACT bool status = listener.validateSourceAddress("alsdkfjgalkdj"); @@ -336,7 +336,7 @@ TEST_F( ClientListenerTest, validateSourceAddress_junk) { EXPECT_EQ(status, false); } -TEST_F( ClientListenerTest, checkSocket) { +TEST_F( TCPClientListenerTest, checkSocket) { // ARRANGE listener.initialize(); int port = listener.getPort(); @@ -349,7 +349,7 @@ TEST_F( ClientListenerTest, checkSocket) { EXPECT_EQ(listener.getPort(), port); } -TEST_F( ClientListenerTest, checkSocket_uninitialized) { +TEST_F( TCPClientListenerTest, checkSocket_uninitialized) { // ARRANGE // ACT int status = listener.checkSocket(); @@ -358,7 +358,7 @@ TEST_F( ClientListenerTest, checkSocket_uninitialized) { EXPECT_EQ(status, -1); } -TEST_F( ClientListenerTest, disconnect) { +TEST_F( TCPClientListenerTest, disconnect) { // ARRANGE listener.initialize(); @@ -369,7 +369,7 @@ TEST_F( ClientListenerTest, disconnect) { EXPECT_EQ(status, 0); } -TEST_F( ClientListenerTest, disconnect_uninitialized) { +TEST_F( TCPClientListenerTest, disconnect_uninitialized) { // ARRANGE // ACT int status = listener.disconnect(); @@ -378,7 +378,7 @@ TEST_F( ClientListenerTest, disconnect_uninitialized) { EXPECT_EQ(status, -1); } -TEST_F( ClientListenerTest, setupNewConnection) { +TEST_F( TCPClientListenerTest, setupNewConnection) { // ARRANGE listener.initialize(); @@ -389,7 +389,7 @@ TEST_F( ClientListenerTest, setupNewConnection) { EXPECT_TRUE(connection != NULL); } -TEST_F( ClientListenerTest, setupNewConnection_uninitialized) { +TEST_F( TCPClientListenerTest, setupNewConnection_uninitialized) { // ARRANGE // ACT diff --git a/trick_source/trick_utils/connection_handlers/test/TCPConnection_test.cpp b/trick_source/trick_utils/connection_handlers/test/TCPConnection_test.cpp index 2c9c92f1..2494a55d 100644 --- a/trick_source/trick_utils/connection_handlers/test/TCPConnection_test.cpp +++ b/trick_source/trick_utils/connection_handlers/test/TCPConnection_test.cpp @@ -107,13 +107,11 @@ TEST_F( TCPConnectionTest, setBlockMode_fcntl_getfl_fail) { socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); return socket_fd; }); - + connection.start(); system_context->register_fcntl_impl([](int a, int b, int c) { errno = EACCES; return -1; }); - connection.start(); - // ACT int status = connection.setBlockMode(true); @@ -131,6 +129,9 @@ TEST_F( TCPConnectionTest, setBlockMode_fcntl_setfl_fail) { socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); return socket_fd; }); + + connection.start(); + system_context->register_fcntl_impl([](int a, int cmd, int c) { if (cmd == F_SETFL) { errno = EBADF; @@ -139,8 +140,6 @@ TEST_F( TCPConnectionTest, setBlockMode_fcntl_setfl_fail) { return 0; }); - connection.start(); - // ACT int status = connection.setBlockMode(true); @@ -240,10 +239,12 @@ TEST_F( TCPConnectionTest, read_nonewline ) { connection.start(); // ACT - std::string result = connection.read(); + std::string result; + int nbytes = connection.read(result); // ASSERT ASSERT_EQ(result, std::string("")); + ASSERT_EQ(nbytes, 0); } @@ -263,12 +264,14 @@ TEST_F( TCPConnectionTest, read ) { connection.start(); // ACT - std::string result = connection.read(); + std::string result; + int nbytes = connection.read(result); // ASSERT std::string expected = "Here is a complete message from a socket\n"; expected += '\0'; ASSERT_EQ(result, expected); + ASSERT_EQ(nbytes, expected.size()); } @@ -285,11 +288,14 @@ TEST_F( TCPConnectionTest, read_nodata ) { connection.start(); // ACT - std::string result = connection.read(); + std::string result; + int nbytes = connection.read(result); + // ASSERT std::string expected = ""; ASSERT_EQ(result, expected); + EXPECT_EQ(nbytes, 0); } TEST_F( TCPConnectionTest, read_other_error ) { @@ -306,11 +312,14 @@ TEST_F( TCPConnectionTest, read_other_error ) { connection.start(); // ACT - std::string result = connection.read(); + std::string result; + int nbytes = connection.read(result); // ASSERT std::string expected = ""; ASSERT_EQ(result, expected); + EXPECT_EQ(nbytes, -1); + } @@ -318,11 +327,14 @@ TEST_F( TCPConnectionTest, read_other_error ) { TEST_F( TCPConnectionTest, read_uninitialized ) { // ARRANGE // ACT - std::string result = connection.read(); + std::string result; + int nbytes = connection.read(result); // ASSERT std::string expected = ""; ASSERT_EQ(result, expected); + ASSERT_EQ(nbytes, 0); + } diff --git a/trick_source/trick_utils/connection_handlers/test/UDPConnection_test.cpp b/trick_source/trick_utils/connection_handlers/test/UDPConnection_test.cpp index af6924f0..a7c7623f 100644 --- a/trick_source/trick_utils/connection_handlers/test/UDPConnection_test.cpp +++ b/trick_source/trick_utils/connection_handlers/test/UDPConnection_test.cpp @@ -317,10 +317,12 @@ TEST_F( UDPConnectionTest, read_nonewline ) { // ACT - std::string result = connection.read(); + std::string result; + int nbytes = connection.read(result); // ASSERT ASSERT_EQ(result, std::string("")); + ASSERT_EQ(nbytes, 0); } @@ -338,12 +340,15 @@ TEST_F( UDPConnectionTest, read ) { connection.start(); // ACT - std::string result = connection.read(); + std::string result; + int nbytes = connection.read(result); // ASSERT std::string expected = "Here is a complete message from a socket\n"; expected += '\0'; ASSERT_EQ(result, expected); + ASSERT_EQ(nbytes, expected.size()); + } @@ -358,11 +363,13 @@ TEST_F( UDPConnectionTest, read_nodata ) { connection.start(); // ACT - std::string result = connection.read(); + std::string result; + int nbytes = connection.read(result); // ASSERT std::string expected = ""; ASSERT_EQ(result, expected); + ASSERT_EQ(nbytes, 0); } TEST_F( UDPConnectionTest, read_other_error ) { @@ -376,19 +383,24 @@ TEST_F( UDPConnectionTest, read_other_error ) { connection.start(); // ACT - std::string result = connection.read(); + std::string result; + int nbytes = connection.read(result); // ASSERT std::string expected = ""; ASSERT_EQ(result, expected); + ASSERT_EQ(nbytes, -1); + } TEST_F( UDPConnectionTest, read_uninitialized ) { // ARRANGE // ACT - std::string result = connection.read(); + std::string result; + int nbytes = connection.read(result); // ASSERT std::string expected = ""; ASSERT_EQ(result, expected); + ASSERT_EQ(nbytes, 0); } diff --git a/trick_source/trick_utils/var_binary_parser/src/var_binary_parser.cc b/trick_source/trick_utils/var_binary_parser/src/var_binary_parser.cc index 4255efa8..d8503316 100644 --- a/trick_source/trick_utils/var_binary_parser/src/var_binary_parser.cc +++ b/trick_source/trick_utils/var_binary_parser/src/var_binary_parser.cc @@ -272,24 +272,6 @@ const size_t ParsedBinaryMessage::variable_name_length_size = 4; const size_t ParsedBinaryMessage::variable_type_size = 4; const size_t ParsedBinaryMessage::variable_size_size = 4; -// Sometimes, we need to reconstruct the size from the message. -// Risk of segfaulting here if the bytes are not correctly formed -int ParsedBinaryMessage::parse (char * raw_bytes) { - std::vector bytes; - unsigned int i = 0; - for ( ; i < message_indicator_size + message_size_size; i++) { - bytes.push_back(raw_bytes[i]); - } - - unsigned int message_size = bytesToInt(std::vector (bytes.begin() + message_indicator_size, bytes.begin() + message_indicator_size + message_size_size), _byteswap); - for ( ; i < message_size + message_indicator_size; i++) { - bytes.push_back(raw_bytes[i]); - } - - return parse(bytes); -} - - int ParsedBinaryMessage::parse (const std::vector& bytes) { if (bytes.size() < header_size) { throw MalformedMessageException(std::string("Not enough bytes in message to contain header: expected at least 12, got " + std::to_string(bytes.size()))); diff --git a/trick_source/trick_utils/var_binary_parser/test/TEST_var_binary_parser.cc b/trick_source/trick_utils/var_binary_parser/test/TEST_var_binary_parser.cc index 1ea19406..0572371e 100644 --- a/trick_source/trick_utils/var_binary_parser/test/TEST_var_binary_parser.cc +++ b/trick_source/trick_utils/var_binary_parser/test/TEST_var_binary_parser.cc @@ -3,7 +3,6 @@ #include #include #include -// #include #include "trick/var_binary_parser.hh" // int hi = 161 @@ -837,7 +836,7 @@ TEST (BinaryParserTest, ParseLong) { // apparently this can be different by platform so we need to be careful here size_t long_size = sizeof(long); std::vector bytes; - for (int i = 0; i < long_size-1; i++) { + for (unsigned int i = 0; i < long_size-1; i++) { bytes.push_back(0x00); } bytes.push_back(0x80); @@ -871,7 +870,7 @@ TEST (BinaryParserTest, ParseUnsignedLong) { // apparently this can be different by platform so we need to be careful here size_t long_size = sizeof(unsigned long); std::vector bytes; - for (int i = 0; i < long_size-1; i++) { + for (unsigned int i = 0; i < long_size-1; i++) { bytes.push_back(0xFF); } bytes.push_back(0x7F); @@ -904,7 +903,7 @@ TEST (BinaryParserTest, ParseLongLong) { // apparently this can be different by platform so we need to be careful here size_t long_long_size = sizeof(long long); std::vector bytes; - for (int i = 0; i < long_long_size-1; i++) { + for (unsigned int i = 0; i < long_long_size-1; i++) { bytes.push_back(0x00); } bytes.push_back(0x80); @@ -937,7 +936,7 @@ TEST (BinaryParserTest, ParseUnsignedLongLong) { // apparently this can be different by platform so we need to be careful here size_t long_long_size = sizeof(unsigned long long); std::vector bytes; - for (int i = 0; i < long_long_size-1; i++) { + for (unsigned int i = 0; i < long_long_size-1; i++) { bytes.push_back(0xFF); } bytes.push_back(0x7F); @@ -1055,7 +1054,7 @@ TEST (BinaryParserTest, ParseWChar) { wchar_t test_wchar = L'J'; std::vector bytes; - for (int i = 0; i < sizeof(wchar_t); i++) { + for (unsigned int i = 0; i < sizeof(wchar_t); i++) { bytes.push_back((unsigned char)((test_wchar >> (i*8)) & 0xFF)); } @@ -1066,11 +1065,17 @@ TEST (BinaryParserTest, ParseWChar) { TEST (BinaryParserTest, ParseWCharWrongType) { Var variable; - std::vector bytes = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xc0}; + wchar_t test_wchar = L'J'; + std::vector bytes; + + for (unsigned int i = 0; i < sizeof(wchar_t); i++) { + bytes.push_back((unsigned char)((test_wchar >> (i*8)) & 0xFF)); + } + variable.setValue(bytes, 8, TRICK_INTEGER, false); try { - variable.getValue(); + variable.getValue(); FAIL() << "Expected exception thrown"; } catch(ParseTypeException& ex) { diff --git a/trickops.py b/trickops.py index a2703249..a04c1843 100644 --- a/trickops.py +++ b/trickops.py @@ -21,16 +21,33 @@ class SimTestWorkflow(TrickWorkflow): trick_dir=trick_top_level, config_file=(trick_top_level + "/test_sims.yml"), cpus=self.cpus, quiet=quiet) def run( self ): build_jobs = self.get_jobs(kind='build') - # Two sims have runs that require ordering via phases: + + # This is awful but I can't think of another way around it + # SIM_test_varserver has 2 tests that should return the code for SIG_USR1, the number is different on Mac vs Linux + # so it can't be hardcoded in the input yml file. Maybe this is a case having a label on a run would be cleaner? + import signal + run_names = ["Run test/SIM_test_varserv RUN_test/err1_test.py", "Run test/SIM_test_varserv RUN_test/err2_test.py"] + for job in [job for job in self.get_jobs(kind='run') if job.name in run_names]: + job._expected_exit_status = signal.SIGUSR1.value + + # Several sims have runs that require ordering via phases: # - SIM_stls dumps a checkpoint that is then read in and checked by a subsequent run # - SIM_checkpoint_data_recording dumps checkpoints that are read by subsequent runs - first_run_jobs = self.get_jobs(kind='run', phase=-1) # Get all jobs with early phase -1 - remaining_run_jobs = self.get_jobs(kind='run', phase=0) # Get all jobs with default phase 0 - analysis_jobs = self.get_jobs(kind='analyze') + # - SIM_test_varserver has 3 runs that cannot be concurrent + # - SIM_mc_generation generates runs and then runs them + phases = [-1, 0, 1, 2, 3] + analysis_jobs = self.get_jobs(kind='analyze') builds_status = self.execute_jobs(build_jobs, max_concurrent=self.cpus, header='Executing all sim builds.') - first_phase_run_status = self.execute_jobs(first_run_jobs, max_concurrent=self.cpus, header="Executing first phase runs.", job_timeout=1000) - runs_status = self.execute_jobs(remaining_run_jobs, max_concurrent=self.cpus, header='Executing remaining runs.', job_timeout=1000) + + jobs = build_jobs + + run_status = 0 + for phase in phases: + run_jobs = self.get_jobs(kind='run', phase=phase) + this_status = self.execute_jobs(run_jobs, max_concurrent=self.cpus, header="Executing phase " + str(phase) + " runs.", job_timeout=1000) + run_status = run_status or this_status + jobs += run_jobs comparison_result = self.compare() analysis_status = self.execute_jobs(analysis_jobs, max_concurrent=self.cpus, header='Executing all analysis.') @@ -39,7 +56,6 @@ class SimTestWorkflow(TrickWorkflow): self.status_summary() # Print a Succinct summary # Dump failing logs - jobs = build_jobs + first_run_jobs + remaining_run_jobs for job in jobs: if job.get_status() == Job.Status.FAILED or job.get_status() == Job.Status.TIMEOUT: print ("*"*120) @@ -54,7 +70,7 @@ class SimTestWorkflow(TrickWorkflow): print(open(job.log_file, "r").read()) print ("*"*120, "\n\n\n") - return (builds_status or runs_status or first_phase_run_status or len(self.config_errors) > 0 or comparison_result or analysis_status) + return (builds_status or run_status or len(self.config_errors) > 0 or comparison_result or analysis_status) if __name__ == "__main__": parser = argparse.ArgumentParser(description='Build, run, and compare all test sims for Trick',