Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions lib/action/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -363,14 +363,13 @@ class ActionClient extends Entity {
* @return {undefined}
*/
destroy() {
if (this._destroyed) {
if (this.isDestroyed()) {
return;
}

this._goalHandles.clear();

this._node._destroyEntity(this, this._node._actionClients);
this._destroyed = true;
}
}

Expand Down
3 changes: 1 addition & 2 deletions lib/action/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ class ActionServer extends Entity {
* @return {undefined}
*/
destroy() {
if (this._destroyed) {
if (this.isDestroyed()) {
return;
}

Expand All @@ -451,7 +451,6 @@ class ActionServer extends Entity {
this._goalHandles.clear();

this._node._destroyEntity(this, this._node._actionServers);
this._destroyed = true;
}
}

Expand Down
40 changes: 40 additions & 0 deletions lib/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,36 @@

'use strict';

// Destroying an entity for which there is immediate i/o activity can
// results in a SEGFAULT. To mitigate this situation we hold a
// reference to released handles to prevent them from being
// GC'ed prematurely.
const OBSOLETE_HANDLES = new Set();
function registerObsoleteHandle(handle) {
OBSOLETE_HANDLES.add(handle);
}

/**
* @class - Class representing a common object in RCL.
* @ignore
*/

class Entity {
/**
* Clears the internal short-lived cache of references to
* destroyed entities.
*
* @ignore
*/
static _gcHandles() {
OBSOLETE_HANDLES.clear();
}

constructor(handle, typeClass, options) {
this._handle = handle;
this._typeClass = typeClass;
this._options = options;
this._destroyed = false;
}

get handle() {
Expand All @@ -49,6 +69,26 @@ class Entity {
get typeClass() {
return this._typeClass;
}

/**
* Release all resources held by this entity.
* Do not call this method directly.
*/
_destroy() {
if (this.isDestroyed()) return;

this._destroyed = true;
this.handle.release();
registerObsoleteHandle(this._handle);
}

/**
* Test if this entity has been destroyed and resources released.
* @returns {boolean} - true when destroyed has previously been called.
*/
isDestroyed() {
return this._destroyed;
}
}

module.exports = Entity;
83 changes: 53 additions & 30 deletions lib/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const Service = require('./service.js');
const Subscription = require('./subscription.js');
const TimeSource = require('./time_source.js');
const Timer = require('./timer.js');
const Entity = require('./entity.js');

// Parameter event publisher constants
const PARAMETER_EVENT_MSG_TYPE = 'rcl_interfaces/msg/ParameterEvent';
Expand Down Expand Up @@ -148,26 +149,26 @@ class Node extends rclnodejs.ShadowNode {
}

execute(handles) {
let timersReady = this._timers.filter(
(timer) => handles.indexOf(timer.handle) !== -1
let timersReady = this._timers.filter((timer) =>
handles.includes(timer.handle)
);
let guardsReady = this._guards.filter(
(guard) => handles.indexOf(guard.handle) !== -1
let guardsReady = this._guards.filter((guard) =>
handles.includes(guard.handle)
);
let subscriptionsReady = this._subscriptions.filter(
(subscription) => handles.indexOf(subscription.handle) !== -1
let subscriptionsReady = this._subscriptions.filter((subscription) =>
handles.includes(subscription.handle)
);
let clientsReady = this._clients.filter(
(client) => handles.indexOf(client.handle) !== -1
let clientsReady = this._clients.filter((client) =>
handles.includes(client.handle)
);
let servicesReady = this._services.filter(
(service) => handles.indexOf(service.handle) !== -1
let servicesReady = this._services.filter((service) =>
handles.includes(service.handle)
);
let actionClientsReady = this._actionClients.filter(
(actionClient) => handles.indexOf(actionClient.handle) !== -1
let actionClientsReady = this._actionClients.filter((actionClient) =>
handles.includes(actionClient.handle)
);
let actionServersReady = this._actionServers.filter(
(actionServer) => handles.indexOf(actionServer.handle) !== -1
let actionServersReady = this._actionServers.filter((actionServer) =>
handles.includes(actionServer.handle)
);

timersReady.forEach((timer) => {
Expand All @@ -177,14 +178,16 @@ class Node extends rclnodejs.ShadowNode {
}
});

subscriptionsReady.forEach((subscription) => {
for (const subscription of subscriptionsReady) {
if (subscription.isDestroyed()) continue;
if (subscription.isRaw) {
let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
if (rawMessage) {
subscription.processResponse(rawMessage);
}
return;
}

this._runWithMessageType(
subscription.typeClass,
(message, deserialize) => {
Expand All @@ -194,13 +197,16 @@ class Node extends rclnodejs.ShadowNode {
}
}
);
});
}

for (const guard of guardsReady) {
if (guard.isDestroyed()) continue;

guardsReady.forEach((guard) => {
guard.callback();
});
}

clientsReady.forEach((client) => {
for (const client of clientsReady) {
if (client.isDestroyed()) continue;
this._runWithMessageType(
client.typeClass.Response,
(message, deserialize) => {
Expand All @@ -213,9 +219,10 @@ class Node extends rclnodejs.ShadowNode {
}
}
);
});
}

servicesReady.forEach((service) => {
for (const service of servicesReady) {
if (service.isDestroyed()) continue;
this._runWithMessageType(
service.typeClass.Request,
(message, deserialize) => {
Expand All @@ -229,9 +236,11 @@ class Node extends rclnodejs.ShadowNode {
}
}
);
});
}

for (const actionClient of actionClientsReady) {
if (actionClient.isDestroyed()) continue;

actionClientsReady.forEach((actionClient) => {
const properties = actionClient.handle.properties;

if (properties.isGoalResponseReady) {
Expand All @@ -242,7 +251,7 @@ class Node extends rclnodejs.ShadowNode {
actionClient.handle,
message
);
if (sequence != null) {
if (sequence != undefined) {
actionClient.processGoalResponse(sequence, deserialize());
}
}
Expand All @@ -257,7 +266,7 @@ class Node extends rclnodejs.ShadowNode {
actionClient.handle,
message
);
if (sequence != null) {
if (sequence != undefined) {
actionClient.processCancelResponse(sequence, deserialize());
}
}
Expand All @@ -272,7 +281,7 @@ class Node extends rclnodejs.ShadowNode {
actionClient.handle,
message
);
if (sequence != null) {
if (sequence != undefined) {
actionClient.processResultResponse(sequence, deserialize());
}
}
Expand Down Expand Up @@ -308,9 +317,11 @@ class Node extends rclnodejs.ShadowNode {
}
);
}
});
}

for (const actionServer of actionServersReady) {
if (actionServer.isDestroyed()) continue;

actionServersReady.forEach((actionServer) => {
const properties = actionServer.handle.properties;

if (properties.isGoalRequestReady) {
Expand Down Expand Up @@ -371,7 +382,11 @@ class Node extends rclnodejs.ShadowNode {
}
GoalInfoArray.freeArray(message);
}
});
}

// At this point it is safe to clear the cache of any
// destroyed entity references
Entity._gcHandles();
}

/**
Expand Down Expand Up @@ -448,11 +463,19 @@ class Node extends rclnodejs.ShadowNode {
}

_destroyEntity(entity, array, syncHandles = true) {
if (entity['isDestroyed'] && entity.isDestroyed()) return;

this._removeEntityFromArray(entity, array);
if (syncHandles) {
this.syncHandles();
}
entity.handle.release();

if (entity['_destroy']) {
entity._destroy();
} else {
// guards and timers
entity.handle.release();
}
}

_validateOptions(options) {
Expand Down
Loading