directory: update do its work inside a thread

A lot of the preparation was needed (and done in previous
months) in making update thread-safe, but here it is.

This was the first thing I made work inside a thread when I
started mpd-uclinux many years ago, and also the last thing I've
done in mainline mpd to work inside a thread, go figure.
This commit is contained in:
Eric Wong 2008-09-23 22:37:18 +02:00 committed by Max Kellermann
parent 0f0ac43b8f
commit 3f0ae13c4b
5 changed files with 60 additions and 152 deletions

View File

@ -819,13 +819,10 @@ static int listHandleUpdate(struct client *client,
char *argv[],
struct strnode *cmdnode, CommandEntry * cmd)
{
static List *pathList;
List *pathList = makeList(NULL, 1);
CommandEntry *nextCmd = NULL;
struct strnode *next = cmdnode->next;
if (!pathList)
pathList = makeList(NULL, 1);
if (argc == 2)
insertInList(pathList, argv[1], NULL);
else
@ -837,25 +834,9 @@ static int listHandleUpdate(struct client *client,
if (cmd != nextCmd) {
int ret = updateInit(pathList);
freeList(pathList);
pathList = NULL;
switch (ret) {
case 0:
if (ret == -1)
command_error(client, ACK_ERROR_UPDATE_ALREADY,
"already updating");
break;
case -1:
command_error(client, ACK_ERROR_SYSTEM,
"problems trying to update");
break;
default:
client_printf(client, "updating_db: %i\n", ret);
ret = 0;
break;
}
return ret;
}
@ -872,26 +853,10 @@ static int handleUpdate(struct client *client,
List *pathList = makeList(NULL, 1);
insertInList(pathList, argv[1], NULL);
ret = updateInit(pathList);
freeList(pathList);
} else
ret = updateInit(NULL);
switch (ret) {
case 0:
command_error(client, ACK_ERROR_UPDATE_ALREADY,
"already updating");
ret = -1;
break;
case -1:
command_error(client, ACK_ERROR_SYSTEM,
"problems trying to update");
break;
default:
client_printf(client, "updating_db: %i\n", ret);
ret = 0;
break;
if (ret == -1)
command_error(client, ACK_ERROR_UPDATE_ALREADY,
"already updating");
return ret;
}
return ret;

View File

@ -53,17 +53,19 @@ enum update_return {
UPDATE_RETURN_UPDATED = 1
};
enum update_progress {
UPDATE_PROGRESS_IDLE = 0,
UPDATE_PROGRESS_RUNNING = 1,
UPDATE_PROGRESS_DONE = 2
} progress;
static Directory *mp3rootDirectory;
static time_t directory_dbModTime;
static sig_atomic_t directory_updatePid;
static pthread_t update_thr;
static sig_atomic_t update_exited;
static sig_atomic_t update_status;
static sig_atomic_t directory_updateJobId;
static int directory_updateJobId;
static DirectoryList *newDirectoryList(void);
@ -116,124 +118,66 @@ static char *getDbFile(void)
int isUpdatingDB(void)
{
return directory_updatePid > 0 ? directory_updateJobId : 0;
return (progress != UPDATE_PROGRESS_IDLE) ? directory_updateJobId : 0;
}
void directory_sigChldHandler(int pid, int status)
void reap_update_task(void)
{
if (directory_updatePid == pid) {
update_status = status;
update_exited = 1;
wakeup_main_task();
}
}
void readDirectoryDBIfUpdateIsFinished(void)
{
int status;
if (!update_exited)
if (progress != UPDATE_PROGRESS_DONE)
return;
status = update_status;
if (WIFSIGNALED(status) && WTERMSIG(status) != SIGTERM) {
ERROR("update process died from a non-TERM signal: %d\n",
WTERMSIG(status));
} else if (!WIFSIGNALED(status)) {
switch (WEXITSTATUS(status)) {
case DIRECTORY_UPDATE_EXIT_UPDATE:
DEBUG("update finished successfully with changes\n");
readDirectoryDB();
DEBUG("update changes read into memory\n");
playlistVersionChange();
case DIRECTORY_UPDATE_EXIT_NOUPDATE:
DEBUG("update exited successfully with no changes\n");
break;
default:
ERROR("error updating db\n");
}
}
update_exited = 0;
directory_updatePid = 0;
pthread_join(update_thr, NULL);
progress = UPDATE_PROGRESS_IDLE;
}
int updateInit(List * pathList)
static void * update_task(void *arg)
{
if (directory_updatePid > 0)
return 0;
List *path_list = (List *)arg;
enum update_return ret = UPDATE_RETURN_NOUPDATE;
/*
* need to block CHLD signal, cause it can exit before we
* even get a chance to assign directory_updatePID
*
* Update: our signal blocking is is utterly broken by
* pthreads(); goal will be to remove dependency on signals;
* but for now use the my_usleep hack below.
*/
blockSignals();
directory_updatePid = fork();
if (directory_updatePid == 0) {
/* child */
enum update_return dbUpdated = UPDATE_RETURN_NOUPDATE;
if (path_list) {
ListNode *node = path_list->firstNode;
unblockSignals();
finishSigHandlers();
closeAllListenSockets();
client_manager_deinit();
finishPlaylist();
finishVolume();
/*
* XXX HACK to workaround race condition where
* directory_updatePid is still zero in the parent even upon
* entry of directory_sigChldHandler.
*/
my_usleep(100000);
if (pathList) {
ListNode *node = pathList->firstNode;
while (node) {
switch (updatePath(node->key)) {
case UPDATE_RETURN_UPDATED:
dbUpdated = UPDATE_RETURN_UPDATED;
break;
case UPDATE_RETURN_NOUPDATE:
break;
case UPDATE_RETURN_ERROR:
exit(DIRECTORY_UPDATE_EXIT_ERROR);
}
node = node->nextNode;
while (node) {
switch (updatePath(node->key)) {
case UPDATE_RETURN_ERROR:
ret = UPDATE_RETURN_ERROR;
goto out;
case UPDATE_RETURN_NOUPDATE:
break;
case UPDATE_RETURN_UPDATED:
ret = UPDATE_RETURN_UPDATED;
}
} else {
dbUpdated = updateDirectory(mp3rootDirectory);
if (dbUpdated == UPDATE_RETURN_ERROR)
exit(DIRECTORY_UPDATE_EXIT_ERROR);
node = node->nextNode;
}
if (dbUpdated == UPDATE_RETURN_NOUPDATE)
exit(DIRECTORY_UPDATE_EXIT_NOUPDATE);
/* ignore signals since we don't want them to corrupt the db */
ignoreSignals();
if (writeDirectoryDB() < 0) {
exit(DIRECTORY_UPDATE_EXIT_ERROR);
}
exit(DIRECTORY_UPDATE_EXIT_UPDATE);
} else if (directory_updatePid < 0) {
unblockSignals();
ERROR("updateInit: Problems forking()'ing\n");
directory_updatePid = 0;
return -1;
free(path_list);
} else {
ret = updateDirectory(mp3rootDirectory);
}
unblockSignals();
if (ret == UPDATE_RETURN_UPDATED && writeDirectoryDB() < 0)
ret = UPDATE_RETURN_ERROR;
out:
progress = UPDATE_PROGRESS_DONE;
wakeup_main_task();
return (void *)ret;
}
int updateInit(List * path_list)
{
pthread_attr_t attr;
if (progress != UPDATE_PROGRESS_IDLE)
return -1;
progress = UPDATE_PROGRESS_RUNNING;
pthread_attr_init(&attr);
if (pthread_create(&update_thr, &attr, update_task, path_list))
FATAL("Failed to spawn update task: %s\n", strerror(errno));
directory_updateJobId++;
if (directory_updateJobId > 1 << 15)
directory_updateJobId = 1;
DEBUG("updateInit: fork()'d update child for update job id %i\n",
DEBUG("updateInit: spawned update thread for update job id %i\n",
(int)directory_updateJobId);
return (int)directory_updateJobId;

View File

@ -34,7 +34,7 @@ typedef struct _Directory {
unsigned stat; /* not needed if ino_t == dev_t == 0 is impossible */
} Directory;
void readDirectoryDBIfUpdateIsFinished(void);
void reap_update_task(void);
int isUpdatingDB(void);

View File

@ -444,7 +444,7 @@ int main(int argc, char *argv[])
COMMAND_RETURN_KILL != handlePendingSignals()) {
syncPlayerAndPlaylist();
client_manager_expire();
readDirectoryDBIfUpdateIsFinished();
reap_update_task();
}
write_state_file();

View File

@ -57,7 +57,6 @@ static void chldSigHandler(mpd_unused int sig)
else
break;
}
directory_sigChldHandler(pid, status);
}
}