blob: 1b50156d4bc30e3912410fd2ec48c004e328488f [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <string.h>
#include "log.h"
#include "parameters.h"
#include "activity.h"
#include "async_handle.h"
#include "async_alarm.h"
#include "async_wait.h"
typedef enum {
ACT_STOPPED,
ACT_PREPARED,
ACT_SCHEDULED,
ACT_STARTED,
ACT_PREPARING,
ACT_PREPARING_STOP,
ACT_STARTING,
ACT_STARTING_STOP,
ACT_STARTING_RESTART,
ACT_STOPPING,
ACT_STOPPING_START
} ActivityState;
typedef struct {
const char *name;
} ActivityStateDescriptor;
static const ActivityStateDescriptor activityStateDescriptors[] = {
[ACT_STOPPED] = {
.name = "stopped"
},
[ACT_PREPARED] = {
.name = "prepared"
},
[ACT_SCHEDULED] = {
.name = "scheduled"
},
[ACT_STARTED] = {
.name = "started"
},
[ACT_PREPARING] = {
.name = "preparing"
},
[ACT_PREPARING_STOP] = {
.name = "preparing+stop"
},
[ACT_STARTING] = {
.name = "starting"
},
[ACT_STARTING_STOP] = {
.name = "starting+stop"
},
[ACT_STARTING_RESTART] = {
.name = "starting+restart"
},
[ACT_STOPPING] = {
.name = "stopping"
},
[ACT_STOPPING_START] = {
.name = "stopping+start"
},
};
struct ActivityObjectStruct {
const ActivityMethods *methods;
void *data;
ActivityState state;
AsyncHandle startAlarm;
};
static const char *
getActivityStateName (ActivityState state) {
if (state < ARRAY_COUNT(activityStateDescriptors)) {
const char *name = activityStateDescriptors[state].name;
if (name && *name) return name;
}
return "unknown";
}
static void
logUnexpectedActivityState (ActivityObject *activity, const char *action) {
ActivityState state = activity->state;
logMessage(LOG_WARNING, "unexpected activity state: %s: %s: %u[%s]",
activity->methods->activityName, action, state, getActivityStateName(state));
}
static void
setActivityState (ActivityObject *activity, ActivityState state) {
logMessage(LOG_DEBUG, "activity state change: %s: %u[%s]",
activity->methods->activityName, state, getActivityStateName(state));
activity->state = state;
}
static void
logActivityActionRequest (ActivityObject *activity, const char *action) {
logMessage(LOG_DEBUG, "activity action request: %s: %s",
activity->methods->activityName, action);
}
static void
logActivityActionFailed (ActivityObject *activity, const char *action) {
logMessage(LOG_DEBUG, "activity action failed: %s: %s",
activity->methods->activityName, action);
}
static void
logActivityActionTimeout (ActivityObject *activity, const char *action) {
logMessage(LOG_DEBUG, "activity action timeout: %s: %s",
activity->methods->activityName, action);
}
static void
cancelActivityStartAlarm (ActivityObject *activity) {
asyncCancelRequest(activity->startAlarm);
activity->startAlarm = NULL;
}
ASYNC_ALARM_CALLBACK(handleActivityStartAlarm) {
ActivityObject *activity = parameters->data;
ActivityStartMethod *start = activity->methods->start;
ActivityState oldState = activity->state;
int started;
ActivityState newState;
setActivityState(activity, ACT_STARTING);
started = !start || start(activity->data);
if (started) {
cancelActivityStartAlarm(activity);
} else {
logActivityActionFailed(activity, "start");
}
newState = activity->state;
setActivityState(activity, (started? ACT_STARTED: oldState));
if (newState == ACT_STARTING_STOP) {
stopActivity(activity);
} else if (newState == ACT_STARTING_RESTART) {
stopActivity(activity);
startActivity(activity);
} else if (newState != ACT_STARTING) {
logUnexpectedActivityState(activity, "starting");
}
}
static int
prepareActivity (ActivityObject *activity) {
ActivityPrepareMethod *prepare = activity->methods->prepare;
ActivityState oldState = activity->state;
if (!prepare) {
setActivityState(activity, ACT_PREPARED);
return 1;
}
setActivityState(activity, ACT_PREPARING);
if (!prepare(activity->data)) {
setActivityState(activity, oldState);
return 0;
}
if (activity->state == ACT_PREPARING) {
setActivityState(activity, ACT_PREPARED);
return 1;
}
if (activity->state == ACT_PREPARING_STOP) {
setActivityState(activity, ACT_STOPPED);
return 0;
}
logUnexpectedActivityState(activity, "preparing");
return 0;
}
static int
scheduleActivity (ActivityObject *activity) {
if (asyncNewRelativeAlarm(&activity->startAlarm, 0, handleActivityStartAlarm, activity)) {
if (asyncResetAlarmInterval(activity->startAlarm, activity->methods->retryInterval)) {
setActivityState(activity, ACT_SCHEDULED);
return 1;
}
cancelActivityStartAlarm(activity);
}
return 0;
}
void
startActivity (ActivityObject *activity) {
logActivityActionRequest(activity, "start");
while (1) {
switch (activity->state) {
case ACT_STOPPED:
if (prepareActivity(activity)) continue;
return;
case ACT_PREPARING_STOP:
setActivityState(activity, ACT_PREPARING);
continue;
case ACT_PREPARED:
if (scheduleActivity(activity)) continue;
return;
case ACT_SCHEDULED:
asyncResetAlarmIn(activity->startAlarm, 0);
return;
case ACT_STARTING_STOP:
setActivityState(activity, ACT_STARTING_RESTART);
continue;
case ACT_STOPPING:
setActivityState(activity, ACT_STOPPING_START);
continue;
case ACT_PREPARING:
case ACT_STARTING:
case ACT_STARTING_RESTART:
case ACT_STARTED:
case ACT_STOPPING_START:
return;
}
logUnexpectedActivityState(activity, "start");
break;
}
}
void
stopActivity (ActivityObject *activity) {
logActivityActionRequest(activity, "stop");
while (1) {
switch (activity->state) {
case ACT_PREPARING:
setActivityState(activity, ACT_PREPARING_STOP);
continue;
case ACT_PREPARED:
setActivityState(activity, ACT_STOPPED);
continue;
case ACT_SCHEDULED:
cancelActivityStartAlarm(activity);
setActivityState(activity, ACT_PREPARED);
continue;
case ACT_STARTING:
case ACT_STARTING_RESTART:
setActivityState(activity, ACT_STARTING_STOP);
continue;
case ACT_STARTED: {
ActivityStopMethod *stop = activity->methods->stop;
if (stop) {
ActivityState newState;
setActivityState(activity, ACT_STOPPING);
stop(activity->data);
newState = activity->state;
setActivityState(activity, ACT_STOPPED);
if (newState == ACT_STOPPING_START) {
startActivity(activity);
} else if (newState != ACT_STOPPING) {
logUnexpectedActivityState(activity, "stopping");
}
} else {
setActivityState(activity, ACT_STOPPED);
}
return;
}
case ACT_STOPPING_START:
setActivityState(activity, ACT_STOPPING);
continue;
case ACT_PREPARING_STOP:
case ACT_STARTING_STOP:
case ACT_STOPPING:
case ACT_STOPPED:
return;
}
logUnexpectedActivityState(activity, "stop");
break;
}
}
ActivityObject *
newActivity (const ActivityMethods *methods, void *data) {
ActivityObject *activity;
if ((activity = malloc(sizeof(*activity)))) {
memset(activity, 0, sizeof(*activity));
activity->methods = methods;
activity->data = data;
activity->state = ACT_STOPPED;
activity->startAlarm = NULL;
return activity;
} else {
logMallocError();
}
return NULL;
}
void
destroyActivity (ActivityObject *activity) {
stopActivity(activity);
awaitActivityStopped(activity);
free(activity);
}
int
isActivityStarted (const ActivityObject *activity) {
return activity->state == ACT_STARTED;
}
int
isActivityStopped (const ActivityObject *activity) {
return activity->state == ACT_STOPPED;
}
ASYNC_CONDITION_TESTER(testActivityStarted) {
const ActivityObject *activity = data;
return isActivityStarted(activity);
}
int
awaitActivityStarted (ActivityObject *activity) {
int timeout = activity->methods->startTimeout;
if (!timeout) timeout = DEFAULT_ACTIVITY_START_TIMEOUT;
if (asyncAwaitCondition(timeout, testActivityStarted, activity)) return 1;
logActivityActionTimeout(activity, "start");
return 0;
}
ASYNC_CONDITION_TESTER(testActivityStopped) {
const ActivityObject *activity = data;
return isActivityStopped(activity);
}
int
awaitActivityStopped (ActivityObject *activity) {
int timeout = activity->methods->stopTimeout;
if (!timeout) timeout = DEFAULT_ACTIVITY_STOP_TIMEOUT;
if (asyncAwaitCondition(timeout, testActivityStopped, activity)) return 1;
logActivityActionTimeout(activity, "stop");
return 0;
}