Skip to content

1.3 Thread Model

Core Semantics

1. Synchronous Calls

  • APIs like Connect() , Read() , and MoveJoint() are synchronous from the caller's perspective.
  • The calling thread waits for the request to complete, then receives the STATUS_CODE or query result.
  • Concurrent network calls on the same Arm instance are serialized in submission order.

2. Periodic Tasks

  • ControllerInfo::AcquireAccess() registers a 2000ms keep-alive timer task.
  • JoggingControl::ContinuousMove() / MultiMove() register 50ms continuous-jogging timer tasks.
  • These periodic tasks reuse the dedicated network thread of the Arm session.
  • When the Arm session stops, the related timer tasks are cleaned up together.

3. Subscribe/Publish (SubPub)

  • TopicPubSub receives WebSocket callbacks through the SDK. The WebSocket library's own threads are not described here.
  • The SDK maintains a messages queue inside SubPubService ; Receive() synchronously fetches the next message from the queue.
  • The queue holds up to 100 messages and drops the oldest ones when full.
  • Multiple Arm / TopicPubSub instances may establish their own WebSocket connections concurrently; other live instances continue independently when one instance is destroyed or disconnected.

SDK Thread View

Thread Safety Rules

Arm Instance

  • Single-thread access: each Arm instance should be accessed by one business thread.
  • Multi-instance concurrency: create multiple Arm instances if you need to control multiple robots concurrently.
  • Not thread-safe: do not share an Arm instance across threads without external synchronization.

Service Modules

All service modules ( motionControl , controllerInfo , alarmClient , etc.) inherit the thread safety characteristics of their parent Arm instance:

cpp
// Safe: access all modules from one business thread
Arm arm;  // Create one Arm session object
arm.Connect("192.168.110.2", "");  // Connect the robot on the current business thread
arm.motionControl.GetCurrentPose(PoseType::JOINT);  // Read the current joint pose serially
arm.controllerInfo.GetCtrlStatus();  // Read controller status serially
arm.alarmClient.GetAllActiveAlarms();  // Read active alarms serially
// Unsafe: multiple threads access the same Arm
// Thread 1
arm.motionControl.MoveJoint(target1, 0.5, 0.5);  // Thread 1 calls the motion API on this Arm// Thread 2 (concurrent)
arm.motionControl.MoveJoint(target2, 0.5, 0.5); // Undefined behavior

Safe Concurrent Access Pattern

cpp
// Safe: use multiple Arm instances for concurrent access
Arm arm1, arm2;  // Two independent Arm sessions
arm1.Connect("192.168.110.2", "");  // Connect the first robot
arm2.Connect("192.168.110.3", "");  // Connect the second robot
// Thread 1
arm1.motion.MoveJoint(target1, 0.5, 0.5);  // Send motion to the first robot
// Thread 2 (concurrent, different robot)
arm2.motion.MoveJoint(target2, 0.5, 0.5); // Safe

Callback Guidelines

Do's

  • Keep callbacks short and fast
  • Copy data if later processing is needed
  • Use thread-safe logging
  • Return quickly so other notifications can proceed

Don'ts

  • Block in callbacks
  • Call blocking SDK APIs from callbacks
  • Access other Arm instances without synchronization
  • Throw exceptions from callbacks

Example: Good Callback Pattern

cpp
// Correct: fast, non-blocking callback
void OnJointPosition(const JointPositionMsg& msg) {  // Enter the callback after a joint-position message arrives
    std::lock_guard<std::mutex> lock(dataMutex_);  // Protect the shared cache
    latestJoints_ = msg.joints;  // Copy the message quickly
}  // Return immediately to avoid blocking the receive thread
void ProcessData() {  // Process cached data on the business thread
    std::vector<Float64> joints;  // Use a local copy for business logic
    {  // Keep the lock scope small
        std::lock_guard<std::mutex> lock(dataMutex_);  // Read the shared cache
        joints = latestJoints_;  // Copy the latest joints to the local variable
    }  // Release the lock so callbacks can continue updating the cache
    // Process data safely outside the lock
}  // End business-thread processing

Example: Bad Callback Pattern

cpp
// Wrong: block inside the callback
void OnAlarm(const AlarmMsg& msg) {  // Enter the callback after an alarm message arrives
    arm.alarmClient.Reset();  // Do not call blocking SDK APIs in callbacks; this risks deadlock
    ProcessLargeData(msg);  // Do not run time-consuming work in callbacks; it blocks other callbacks
}  // End the incorrect callback example
  • Manage Connect() / Disconnect() / mode switching for the same Arm from one business thread or one scheduling path.
  • In TopicPubSub callbacks, only do lightweight work such as parsing key fields, enqueuing data, or setting flags.
  • Run time-consuming logic, secondary control commands, and reconnection actions in business threads instead of blocking inside callbacks.

Constraints

Prohibited Reentrant Operations

Do not call the following from TopicPubSub callbacks:

  • Arm::Connect() , Arm::Disconnect()
  • TopicPubSub::Connect() , TopicPubSub::Disconnect()
  • StartReceiving() , RemoveMessageHandler()

In the current implementation, these reentrant operations may return OTHER_ERR or be ignored.

Other Constraints

  • Do not assume different facades will hit the same robot in parallel; requests on the same Arm are safer to understand as serialized by default.
  • If your application requires high throughput or device isolation, use multiple Arm / ArmHandle* sessions.

C99 Notes

  • Call Arm_Disconnect() before Arm_Destroy() so the session's network thread and timer tasks are cleaned up normally.

Performance Considerations

Connection Recovery

If the network thread detects disconnection:

  • The caller handles recovery by calling Disconnect() and then Connect()
  • All pending operations return error codes

Best Practices Summary

  1. Data copy: copy data in callbacks and process it in worker threads
  2. Error handling: always check return codes and handle disconnects gracefully
  3. Resource cleanup: always call Disconnect() before destroying an Arm instance