Commit 1c4d4965 authored by Éric Thiébaut's avatar Éric Thiébaut
Browse files

Remote objects have a distinct state and command members

The remote objects all use the same state and command constants for a
more uniform behavior.  All commands have a time limit.  The kill
command is considered as successful if the server was already killed.
parent cab8ad8b
......@@ -60,10 +60,8 @@ static tao_status on_reset(
static void cleanup(void)
{
if (dm != NULL) {
if (dm->base.task != TAO_TASK_KILL) {
// FIXME: should lock
dm->base.task = TAO_TASK_KILL;
}
dm->base.command = TAO_COMMAND_NONE;
dm->base.state = TAO_STATE_KILLED;
tao_remote_mirror_detach(dm);
dm = NULL;
}
......
......@@ -67,7 +67,7 @@ static tao_status process_frame(
const tao_acquisition_buffer* buf);
static void change_state(
tao_camera_state state);
tao_state state);
static void wrlock_shared_camera(
void);
......@@ -120,7 +120,7 @@ static struct {
tao_mutex mutex; // Mutex to lock this structure.
tao_cond cond; // Condition variable to wait for.
} monitor = {TAO_MUTEX_INITIALIZER, TAO_COND_INITIALIZER};
static volatile tao_camera_state camera_state = TAO_CAMERA_STATE_INITIALIZING;
static volatile tao_state camera_state = TAO_STATE_INITIALIZING;
static volatile enum {
INITIALIZING,
SLEEPING,
......@@ -345,7 +345,7 @@ int main(
shcam = tao_get_framegrabber_shared_camera(vfg);
wrlock_shared_camera();
(void)tao_reflect_camera_information(shcam, dev);
shcam->info.state = TAO_CAMERA_STATE_INITIALIZING;
shcam->info.state = TAO_STATE_INITIALIZING;
unlock_shared_camera();
if (tao_any_errors(NULL)) {
tao_report_error();
......@@ -386,7 +386,7 @@ int main(
tao_status status = TAO_OK;
lock_monitor();
on_processing = process_frame;
camera_state = TAO_CAMERA_STATE_INITIALIZING;
camera_state = TAO_STATE_INITIALIZING;
worker_state = INITIALIZING;
code = pthread_create(&worker, NULL, run_worker, NULL);
if (code != 0) {
......@@ -405,7 +405,7 @@ int main(
while (!quitting && worker_state == INITIALIZING) {
if (debuglevel > 0) {
debug(CONTROL_DEBUGLEVEL, "Server: State is \"%s\"...",
tao_get_camera_state_name(camera_state));
tao_state_get_name(camera_state));
}
wait_monitor();
}
......@@ -413,7 +413,7 @@ int main(
status = TAO_ERROR;
}
if (status == TAO_OK) {
change_state(TAO_CAMERA_STATE_SLEEPING);
change_state(TAO_STATE_WAITING);
}
unlock_monitor();
if (status != TAO_OK) {
......@@ -481,7 +481,7 @@ static void on_return(
// no longer reachable.
debug(CONTROL_DEBUGLEVEL, "Finalize frame-grabber...");
wrlock_shared_camera();
shcam->info.state = TAO_CAMERA_STATE_FINISHED;
shcam->info.state = TAO_STATE_KILLED;
unlock_shared_camera();
(void)tao_destroy_framegrabber(vfg);
vfg = NULL;
......@@ -535,22 +535,22 @@ static void debug(
// Yields error message corresponding to an invalid state.
static const char* invalid_state(
tao_camera_state state)
tao_state state)
{
switch (state) {
case TAO_CAMERA_STATE_INITIALIZING:
case TAO_STATE_INITIALIZING:
return "Camera has not been initialized";
case TAO_CAMERA_STATE_SLEEPING:
case TAO_STATE_WAITING:
return "Acquisition has not been started";
case TAO_CAMERA_STATE_STARTING:
case TAO_STATE_STARTING:
return "Acquisition is about to start";
case TAO_CAMERA_STATE_ACQUIRING:
case TAO_STATE_WORKING:
return "Acquisition is running";
case TAO_CAMERA_STATE_STOPPING:
case TAO_STATE_STOPPING:
return "Acquisition is about to stop";
case TAO_CAMERA_STATE_ABORTING:
case TAO_STATE_ABORTING:
return "Acquisition is about to abort";
case TAO_CAMERA_STATE_FINISHED:
case TAO_STATE_KILLED:
return "Camera has been closed";
default:
return "Camera in improper state";
......@@ -563,13 +563,13 @@ static const char* invalid_state(
// frame). When a new scheduled task is taken into account by the worker
// thread, it changes the value of its `state` parameter and when the task is
// completed,it changes the value of its `command` parameter.
#define STATE_INITIALIZING TAO_CAMERA_STATE_INITIALIZING
#define STATE_SLEEPING TAO_CAMERA_STATE_SLEEPING
#define STATE_STARTING TAO_CAMERA_STATE_STARTING
#define STATE_ACQUIRING TAO_CAMERA_STATE_ACQUIRING
#define STATE_STOPPING TAO_CAMERA_STATE_STOPPING
#define STATE_ABORTING TAO_CAMERA_STATE_ABORTING
#define STATE_FINISHED TAO_CAMERA_STATE_FINISHED
#define STATE_INITIALIZING TAO_STATE_INITIALIZING
#define STATE_SLEEPING TAO_STATE_WAITING
#define STATE_STARTING TAO_STATE_STARTING
#define STATE_ACQUIRING TAO_STATE_WORKING
#define STATE_STOPPING TAO_STATE_STOPPING
#define STATE_ABORTING TAO_STATE_ABORTING
#define STATE_FINISHED TAO_STATE_KILLED
// Callback for processing acquisition buffers.
typedef void processor(
......@@ -746,12 +746,12 @@ static tao_status process_frame(
// been locked by the caller but not the shared resources (the shared camera).
// The other thread is notified of the change.
static void change_state(
tao_camera_state state)
tao_state state)
{
if (debuglevel > 0) {
debug(COMMAND_DEBUGLEVEL, "Changing state from \"%s\" to \"%s\"...",
tao_get_camera_state_name(camera_state),
tao_get_camera_state_name(state));
tao_state_get_name(camera_state),
tao_state_get_name(state));
}
camera_state = state;
wrlock_shared_camera();
......@@ -767,7 +767,7 @@ static void* run_worker(
lock_monitor();
while (true) {
// Wait until quitting or starting acquisition.
while (camera_state != TAO_CAMERA_STATE_ACQUIRING && !quitting) {
while (camera_state != TAO_STATE_WORKING && !quitting) {
worker_state = SLEEPING;
wait_monitor();
}
......@@ -797,7 +797,7 @@ static void* run_worker(
// ownership until the acquisition buffer has been released. The
// address of the processing callback must be recorded before
// unlocking the monitor.
if (camera_state != TAO_CAMERA_STATE_ABORTING && !suspended && !quitting) {
if (camera_state != TAO_STATE_ABORTING && !suspended && !quitting) {
unlock_monitor();
status = on_processing(buf);
lock_monitor();
......@@ -1042,10 +1042,10 @@ static tao_status on_get_state(
return TAO_ERROR;
}
rdlock_shared_camera();
tao_camera_state state = shcam->info.state;
tao_state state = shcam->info.state;
unlock_shared_camera();
if (tao_print_to_server_buffer(
srv, "%s\n", tao_get_camera_state_name(state)) != TAO_OK) {
srv, "%s\n", tao_state_get_name(state)) != TAO_OK) {
return TAO_ERROR;
}
return tao_set_reply_data_from_buffer(srv, bufptr, sizptr, false);
......@@ -1181,7 +1181,7 @@ static tao_status check_recv_args(
}
// Caller (server/worker) shall own the camera.
static tao_camera_state get_stable_state(
static tao_state get_stable_state(
tao_camera* dev,
int reset)
{
......@@ -1193,13 +1193,13 @@ static tao_camera_state get_stable_state(
--reset;
}
if (dev->runlevel == 0) {
return TAO_CAMERA_STATE_INITIALIZING;
return TAO_STATE_INITIALIZING;
} else if (dev->runlevel == 1) {
return TAO_CAMERA_STATE_SLEEPING;
return TAO_STATE_WAITING;
} else if (dev->runlevel == 2) {
return TAO_CAMERA_STATE_ACQUIRING;
return TAO_STATE_WORKING;
} else {
return TAO_CAMERA_STATE_FINISHED;
return TAO_STATE_KILLED;
}
}
......@@ -1207,15 +1207,15 @@ static tao_camera_state get_stable_state(
// commands. The argument if one of the possible transient camera state. The
// caller (server) shall have locked the monitor.
static void trigger_acquisition(
tao_camera_state transient)
tao_state transient)
{
// Are we starting acquisition?
bool starting = (transient == TAO_CAMERA_STATE_STARTING);
bool starting = (transient == TAO_STATE_STARTING);
// Maybe nothing to do.
if (quitting || camera_state == transient ||
camera_state == (starting ? TAO_CAMERA_STATE_ACQUIRING :
TAO_CAMERA_STATE_SLEEPING)) {
camera_state == (starting ? TAO_STATE_WORKING :
TAO_STATE_WAITING)) {
return;
}
......@@ -1346,12 +1346,12 @@ static tao_status on_set_start(
tao_status status = check_recv_args(srv, argc, argv, buf, siz);
if (status == TAO_OK) {
lock_monitor();
trigger_acquisition(TAO_CAMERA_STATE_STARTING);
if (camera_state == TAO_CAMERA_STATE_ACQUIRING) {
trigger_acquisition(TAO_STATE_STARTING);
if (camera_state == TAO_STATE_WORKING) {
// If successful, starting acquisition resume acquisition.
suspended = false;
} else {
set_static_message(srv, (camera_state == TAO_CAMERA_STATE_FINISHED ?
set_static_message(srv, (camera_state == TAO_STATE_KILLED ?
"Camera has been closed" :
"Starting acquisition has failed"));
status = TAO_ERROR;
......@@ -1372,9 +1372,9 @@ static tao_status on_set_stop(
tao_status status = check_recv_args(srv, argc, argv, buf, siz);
if (status == TAO_OK) {
lock_monitor();
trigger_acquisition(TAO_CAMERA_STATE_STOPPING);
if (camera_state != TAO_CAMERA_STATE_SLEEPING) {
set_static_message(srv, (camera_state == TAO_CAMERA_STATE_FINISHED ?
trigger_acquisition(TAO_STATE_STOPPING);
if (camera_state != TAO_STATE_WAITING) {
set_static_message(srv, (camera_state == TAO_STATE_KILLED ?
"Camera has been closed" :
"Stopping acquisition has failed"));
status = TAO_ERROR;
......@@ -1395,9 +1395,9 @@ static tao_status on_set_abort(
tao_status status = check_recv_args(srv, argc, argv, buf, siz);
if (status == TAO_OK) {
lock_monitor();
trigger_acquisition(TAO_CAMERA_STATE_ABORTING);
if (camera_state != TAO_CAMERA_STATE_SLEEPING) {
set_static_message(srv, (camera_state == TAO_CAMERA_STATE_FINISHED ?
trigger_acquisition(TAO_STATE_ABORTING);
if (camera_state != TAO_STATE_WAITING) {
set_static_message(srv, (camera_state == TAO_STATE_KILLED ?
"Camera has been closed" :
"Aborting acquisition has failed"));
status = TAO_ERROR;
......@@ -1419,8 +1419,8 @@ static tao_status on_set_quit(
return TAO_ERROR;
}
lock_monitor();
trigger_acquisition(TAO_CAMERA_STATE_ABORTING);
change_state(TAO_CAMERA_STATE_FINISHED);
trigger_acquisition(TAO_STATE_ABORTING);
change_state(TAO_STATE_KILLED);
quitting = true;
unlock_monitor();
return TAO_OK;
......@@ -1570,8 +1570,8 @@ static tao_status on_set_config(
lock_monitor(); do { // This loop is to unlock the monitor on return.
// Acquisition must not be running or about to start.
if (camera_state == TAO_CAMERA_STATE_ACQUIRING ||
camera_state == TAO_CAMERA_STATE_STARTING) {
if (camera_state == TAO_STATE_WORKING ||
camera_state == TAO_STATE_STARTING) {
set_static_message(srv, invalid_state(camera_state));
status = TAO_ERROR;
break;
......@@ -1583,7 +1583,7 @@ static tao_status on_set_config(
}
if (quitting) {
set_static_message(srv, "Camera server is about to quit");
change_state(TAO_CAMERA_STATE_FINISHED);
change_state(TAO_STATE_KILLED);
status = TAO_ERROR;
break;
}
......
......@@ -21,7 +21,6 @@
#include <math.h>
#include <string.h>
#define INVALID_CAMERA_STATE -1
#define FRAMEGRABBER_TRY_LOCK 0
static inline void fatal_error(
......@@ -114,32 +113,6 @@ bool tao_parse_camera_roi_option(
return false;
}
//-----------------------------------------------------------------------------
// CAMERA STATE
const char* tao_get_camera_state_name(
tao_camera_state state)
{
switch (state) {
case TAO_CAMERA_STATE_INITIALIZING:
return "initializing";
case TAO_CAMERA_STATE_SLEEPING:
return "sleeping";
case TAO_CAMERA_STATE_STARTING:
return "starting";
case TAO_CAMERA_STATE_ACQUIRING:
return "acquiring";
case TAO_CAMERA_STATE_STOPPING:
return "stopping";
case TAO_CAMERA_STATE_ABORTING:
return "aborting";
case TAO_CAMERA_STATE_FINISHED:
return "finished";
default:
return "unknown";
}
}
//-----------------------------------------------------------------------------
// UNIFIED CAMERAS
......@@ -171,7 +144,7 @@ static void initialize_camera_info(
info->config.framerate = 25.0; // 25 Hz
info->config.exposuretime = 0.001; // 1 ms
info->config.weighted = false;
info->state = TAO_CAMERA_STATE_INITIALIZING;
info->state = TAO_STATE_INITIALIZING;
info->temperature = NAN;
get_monotonic_time(&info->origin);
info->frames = 0;
......@@ -265,12 +238,12 @@ tao_camera* tao_create_camera(
cam->ctx = ctx;
initialize_camera_info(&cam->info);
cam->runlevel = 0;
cam->info.state = TAO_CAMERA_STATE_INITIALIZING;
cam->info.state = TAO_STATE_INITIALIZING;
if (cam->ops->initialize(cam) != TAO_OK) {
goto error3;
}
cam->runlevel = 1;
cam->info.state = TAO_CAMERA_STATE_SLEEPING;
cam->info.state = TAO_STATE_WAITING;
return cam;
error3:
......@@ -288,16 +261,16 @@ tao_status tao_destroy_camera(
if (cam != NULL) {
// Stop acquisition if acquiring (ignoring errors).
if (cam->runlevel == 2) {
cam->info.state = TAO_CAMERA_STATE_STOPPING;
cam->info.state = TAO_STATE_STOPPING;
cam->ops->stop(cam);
cam->runlevel = 1;
cam->info.state = TAO_CAMERA_STATE_SLEEPING;
cam->info.state = TAO_STATE_WAITING;
}
// Call virtual "finalize" method.
cam->ops->finalize(cam);
cam->runlevel = 4;
cam->info.state = TAO_CAMERA_STATE_FINISHED;
cam->info.state = TAO_STATE_KILLED;
// Free acquisition buffers **after** calling the "finalize" virtual
// method to make sure that these buffers are not in use somewhere.
......@@ -399,20 +372,20 @@ static tao_status fix_camera_state(
// Recoverable error. Attempt to reset the camera.
if (cam->ops->reset(cam) != TAO_OK || cam->runlevel != 1) {
cam->runlevel = 4;
cam->info.state = TAO_CAMERA_STATE_FINISHED;
cam->info.state = TAO_STATE_KILLED;
tao_store_error(__func__, TAO_UNRECOVERABLE);
return TAO_ERROR;
}
}
if (cam->runlevel <= 0) {
cam->info.state = TAO_CAMERA_STATE_INITIALIZING;
cam->info.state = TAO_STATE_INITIALIZING;
} else if (cam->runlevel == 1) {
cam->info.state = TAO_CAMERA_STATE_SLEEPING;
cam->info.state = TAO_STATE_WAITING;
} else if (cam->runlevel == 2) {
cam->info.state = TAO_CAMERA_STATE_ACQUIRING;
cam->info.state = TAO_STATE_WORKING;
} else {
// Unrecoverable error.
cam->info.state = TAO_CAMERA_STATE_FINISHED;
cam->info.state = TAO_STATE_KILLED;
}
return TAO_OK;
}
......@@ -446,13 +419,13 @@ tao_status tao_start_acquisition(
// run-level and report success. In case of errors, the run-level is
// left unchanged but the virtual method is allowed to set it to a more
// suitable value which we reflect in the camera state.
cam->info.state = TAO_CAMERA_STATE_STARTING;
cam->info.state = TAO_STATE_STARTING;
if (cam->ops->start(cam) != TAO_OK) {
fix_camera_state(cam);
return TAO_ERROR;
}
cam->runlevel = 2;
cam->info.state = TAO_CAMERA_STATE_ACQUIRING;
cam->info.state = TAO_STATE_WORKING;
return TAO_OK;
case 2:
......@@ -488,13 +461,13 @@ tao_status tao_stop_acquisition(
// run-level and report success. In case of errors, the run-level is
// left unchanged but the virtual method is allowed to set it to a more
// suitable value which we reflect in the camera state.
cam->info.state = TAO_CAMERA_STATE_STOPPING;
cam->info.state = TAO_STATE_STOPPING;
if (cam->ops->stop(cam) != TAO_OK) {
fix_camera_state(cam);
return TAO_ERROR;
}
cam->runlevel = 1;
cam->info.state = TAO_CAMERA_STATE_SLEEPING;
cam->info.state = TAO_STATE_WAITING;
return TAO_OK;
default:
......@@ -585,7 +558,7 @@ tao_status tao_reset_camera(
switch (cam->runlevel) {
case 1:
// Camera is not acquiring, nothing else to do.
cam->info.state = TAO_CAMERA_STATE_SLEEPING;
cam->info.state = TAO_STATE_WAITING;
return TAO_OK;
case 2:
......@@ -596,11 +569,11 @@ tao_status tao_reset_camera(
// Recoverable error. Attempt to reset the camera.
if (cam->ops->reset(cam) != TAO_OK || cam->runlevel != 1) {
cam->runlevel = 4;
cam->info.state = TAO_CAMERA_STATE_FINISHED;
cam->info.state = TAO_STATE_KILLED;
tao_store_error(__func__, TAO_UNRECOVERABLE);
return TAO_ERROR;
}
cam->info.state = TAO_CAMERA_STATE_SLEEPING;
cam->info.state = TAO_STATE_WAITING;
return TAO_OK;
default:
......@@ -769,8 +742,8 @@ tao_status tao_print_camera_information(
if (tao_print_camera_configuration(out, &inf->config) != TAO_OK) {
return TAO_ERROR;
}
if (fprintf(out, "Camera state: %s\n",
tao_get_camera_state_name(inf->state)) < 0 ||
if (fprintf(out, "Camera server state: %s\n",
tao_state_get_name(inf->state)) < 0 ||
fprintf(out, "Sensor temperature: %.1f°C\n",
inf->temperature) < 0 ||
fprintf(out, "Origin of time: %s s\n",
......@@ -1101,7 +1074,7 @@ GETTER(double, info.config, exposuretime, 0.0)
GETTER(tao_eltype, info.config, pixeltype, TAO_UINT8)
GETTER(tao_encoding, info.config, sensorencoding, TAO_ENCODING_UNKNOWN)
GETTER(tao_encoding, info.config, bufferencoding, TAO_ENCODING_UNKNOWN)
GETTER(tao_camera_state, info, state, INVALID_CAMERA_STATE)
GETTER(tao_state, info, state, TAO_STATE_KILLED)
#undef GETTER
tao_serial tao_get_image_list_length(
......
......@@ -70,10 +70,14 @@ static inline double* remote_mirror_get_act_cmds(
obj, obj->refs_offset + 2*obj->nacts*sizeof(double));
}
static inline bool is_running(
// Check whether the server owning the remote object is alive.
//
// NOTE: Since server state is an *atomic* variable, the caller may not have
// locked the object.
static inline bool is_alive(
const tao_remote_mirror* obj)
{
return (obj->base.task != TAO_TASK_KILL);
return (obj->base.state != TAO_STATE_KILLED);
}
static inline tao_dataframe_header* fetch_frame(
......@@ -224,124 +228,120 @@ double *tao_remote_mirror_get_actual_commands(
return (obj == NULL) ? NULL : remote_mirror_get_act_cmds(obj);
}
tao_status tao_remote_mirror_kill(
tao_remote_mirror* obj)
// Inline function to check whether a client should wait for the remote server
// to be ready to accept commands.
//
// NOTE: Since communication is asynchronous, we can assume that a server may
// receive at most one command while initializing.
static inline bool wait_for_command(
tao_status status,
const tao_remote_mirror* obj)
{
if (obj == NULL) {
tao_store_error(__func__, TAO_BAD_ADDRESS);
return TAO_ERROR;
}
tao_status status = tao_remote_mirror_lock(obj);
if (status != TAO_OK) {
return status;
}
while (status == TAO_OK && obj->base.task != TAO_TASK_IDLE
&& obj->base.task != TAO_TASK_KILL) {
status = tao_remote_mirror_wait_condition(obj);
}
if (status == TAO_OK) {
if (obj->base.task == TAO_TASK_IDLE) {
obj->base.task = TAO_TASK_KILL;
if (tao_remote_mirror_broadcast_condition(obj) != TAO_OK) {
status = TAO_ERROR;
}
} else if (obj->base.task != TAO_TASK_KILL) {
tao_store_error(__func__, TAO_ASSERTION_FAILED);
status = TAO_ERROR;
}
}
if (tao_remote_mirror_unlock(obj) != TAO_OK) {
status = TAO_ERROR;
}
return status;
return (status == TAO_OK && is_alive(obj)
&& obj->base.command != TAO_COMMAND_NONE);
}
// Wait for the remote deformable mirror to be ready for executing a new task.
// On entry, the remote deformable mirror must not have been locked by the
// caller. On successful return, the remote deformable mirror is locked by the
// caller.
static tao_status wait_idle(
// Inline function to check whether a client should wait for the requested
// frame to be available.
static inline bool wait_for_frame(
tao_status status,
const tao_remote_mirror* obj,
tao_serial serial)
{
return (status == TAO_OK && is_alive(obj)
&& obj->base.serial < serial);
}
// Attempt to lock the remote instance and to wait for its server to accept
// commands or to be killed. On return, `*locked` indicates whether the remote
// instance has been locked by the caller. It the returned status is `TAO_OK`,
// the lock has been acquired and the server shall be either idle or killed.
static tao_status lock_and_wait_for_command(
bool* locked,
tao_remote_mirror* obj,
double secs)
{
// Convert seconds to absolute time, then lock resources and wait until
// server ready for a new task.
// the server be ready for a new command.
tao_status status;
tao_time abstime;
switch (tao_get_absolute_timeout(&abstime, secs)) {
case TAO_TIMEOUT_NEVER:
status = tao_remote_mirror_lock(obj);
if (status != TAO_OK) {
return status;
}
while (status == TAO_OK && obj->base.task != TAO_TASK_IDLE
&& obj->base.task != TAO_TASK_KILL) {
*locked = (status == TAO_OK);
while (wait_for_command(status, obj)) {
status = tao_remote_mirror_wait_condition(obj);
}
break;
case TAO_TIMEOUT_FUTURE:
status = tao_remote_mirror_abstimed_lock(obj, &abstime);
if (status != TAO_OK) {
return status;
}
while (status == TAO_OK && obj->base.task != TAO_TASK_IDLE
&& obj->base.task != TAO_TASK_KILL) {
*locked = (status == TAO_OK);
while (wait_for_command(status, obj)) {
status = tao_remote_mirror_abstimed_wait_condition(obj, &abstime);
}
break;
case TAO_TIMEOUT_NOW:
status = tao_remote_mirror_try_lock(obj);
if (status != TAO_OK) {
return status;
*locked = (status == TAO_OK);
if (wait_for_command(status, obj)) {
status = TAO_TIMEOUT;
}
break;
case TAO_TIMEOUT_PAST:
return TAO_TIMEOUT;
default: // TAO_TIMEOUT_ERROR
return TAO_ERROR;
}
// Check result and unlock resources in case of failure.
if (status == TAO_OK && obj->base.task != TAO_TASK_IDLE) {
*locked = false;
status = TAO_TIMEOUT;
}
if (status != TAO_OK) {
if (tao_remote_mirror_unlock(obj) != TAO_OK) {
status = TAO_ERROR;
}
break;
default: // TAO_TIMEOUT_ERROR
*locked = false;
status = TAO_ERROR;
break;
}
return status;
}
tao_status tao_remote_mirror_reset(
static inline tao_status send_command(
const char* func,
tao_remote_mirror* obj,
tao_command command,
double secs)