7.2. Threading model

All operations on different Connector() instances are thread-safe.

Operations on the same Connector() instance or any contained Input() or Output() are, in general, not protected for multi-threaded access. The only exceptions are the following wait operations with the caveats mentioned below.

Note

If you are using the event-based functionality (e.g., connector.on(on_data_available, () => {})), refer to the additional restraints described in the Additional considerations when using event-based functionality section below.

Thread-safe operations:

Note

All of the operations listed above are asynchronous (and return a Promise which will eventually be resolved or rejected).

Note

Output.write() can block the current thread under certain circumstances, but Output.write() is not thread-safe.

Warning

Input.wait() and Input.waitForPublications() are not reentrant (it is currently not possible to have more than one Promise pending on Input.wait() or Input.waitForPublications()). Since internally the same resource is used for both of these operations, it is not possible to wait on both Input.wait() or Input.waitForPublications() simultaneously.

For example, the following code will throw an DDSError():

const waitForDiscovery = async () => {
   try {
      await input.waitForSubscriptions()
   } catch (err) {
      console.log('Caught error: ' + err)
   }
}

const wait = async () => {
   try {
      await input.wait()
   } catch (err) {
      console.log('Caught error: ' + err)
   }
}

waitForDiscovery()
wait()

The input.wait call within the asynchronous function wait will fail since there is a simultaneous request to input.waitForSubscriptions. This can be avoided by ensuring you only have a single wait operation pending at a time:

const waitForDiscovery = async () => {
   try {
      await input.waitForSubscriptions()
   } catch (err) {
      console.log('Caught error: ' + err)
   }
}

const wait = async () => {
   try {
      await input.wait()
   } catch (err) {
      console.log('Caught error: ' + err)
   }
}

const myApplication = async () => {
   await waitForDiscovery()
   await wait()
}

myApplication()

The same limitation exists between Output.wait() and Output.waitForSubscriptions().

7.2.1. Additional considerations when using event-based functionality

If using event-based notifications (that is, if you have installed a listener for the on_data_available event on a Connector(), as explained in Reading or taking the data), there are additional restrictions to be aware of.

It is possible to install multiple listeners for the on_data_available event:

connector.on('on_data_available', () => {
  // Read the samples so that they remain available within the Input
  input.read()
  doSomething(input.samples)
})
connector.on('on_data_available', () => {
  // Take the samples to remove them from the Input. Since event callbacks
  // are, by default, run in the order they are registered, there is no risk
  // that this is run before the above listener
  input.take()
  doSomethingElse(input.samples)
})

In the above example, when data is received, both the installed callbacks will be run. These callbacks are run sequentially, so it is not necessary to protect them manually at an application level.

It is not possible to call Connector.wait() if there is an installed listener for the on_data_available event. This is due to the fact that while the on_data_available listener is installed, the resource required internally for Connector.wait() is busy.

In your application, if you remove all the registered listeners for on_data_available and later need to re-add them (or wait for data using Connector.wait()), it is necessary to call Connector.waitForCallbackFinalization(). This method returns a Promise that will resolve once the resources used internally by the Connector() are no longer in use:

connector.on('on_data_available', handleDataCallback)
connector.off('on_data_available', handleDataCallback)
// Since we removed the only callback for the on_data_available event, we must
// now wait for the Promise to resolve before re-adding a new callback
await connector.waitForCallbackFinalization()
connector.on('on_data_available', newHandleDataCallback)

Warning

It is important to note that Connector.waitForCallbackFinalization() does not free any resources. It should only be used for notification of when a Connector() can be re-used for other wait operations. It is still necessary to call Connector.close() to free the resources.

If you install a on_data_available listener, you need to wait for the Promise returned by Connector.close() to resolve before continuing with the application shutdown procedure. This allows the Connector() to synchronize its shutdown with the listener:

connector.close()
  .then(() => {
    // continue with application shutdown
  })