Commit 3f0ae13c authored by Eric Wong's avatar Eric Wong Committed by Max Kellermann

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.
parent 0f0ac43b
...@@ -819,13 +819,10 @@ static int listHandleUpdate(struct client *client, ...@@ -819,13 +819,10 @@ static int listHandleUpdate(struct client *client,
char *argv[], char *argv[],
struct strnode *cmdnode, CommandEntry * cmd) struct strnode *cmdnode, CommandEntry * cmd)
{ {
static List *pathList; List *pathList = makeList(NULL, 1);
CommandEntry *nextCmd = NULL; CommandEntry *nextCmd = NULL;
struct strnode *next = cmdnode->next; struct strnode *next = cmdnode->next;
if (!pathList)
pathList = makeList(NULL, 1);
if (argc == 2) if (argc == 2)
insertInList(pathList, argv[1], NULL); insertInList(pathList, argv[1], NULL);
else else
...@@ -837,25 +834,9 @@ static int listHandleUpdate(struct client *client, ...@@ -837,25 +834,9 @@ static int listHandleUpdate(struct client *client,
if (cmd != nextCmd) { if (cmd != nextCmd) {
int ret = updateInit(pathList); int ret = updateInit(pathList);
freeList(pathList); if (ret == -1)
pathList = NULL;
switch (ret) {
case 0:
command_error(client, ACK_ERROR_UPDATE_ALREADY, command_error(client, ACK_ERROR_UPDATE_ALREADY,
"already updating"); "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; return ret;
} }
...@@ -872,26 +853,10 @@ static int handleUpdate(struct client *client, ...@@ -872,26 +853,10 @@ static int handleUpdate(struct client *client,
List *pathList = makeList(NULL, 1); List *pathList = makeList(NULL, 1);
insertInList(pathList, argv[1], NULL); insertInList(pathList, argv[1], NULL);
ret = updateInit(pathList); ret = updateInit(pathList);
freeList(pathList); if (ret == -1)
} else
ret = updateInit(NULL);
switch (ret) {
case 0:
command_error(client, ACK_ERROR_UPDATE_ALREADY, command_error(client, ACK_ERROR_UPDATE_ALREADY,
"already updating"); "already updating");
ret = -1; return ret;
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; return ret;
......
...@@ -53,17 +53,19 @@ enum update_return { ...@@ -53,17 +53,19 @@ enum update_return {
UPDATE_RETURN_UPDATED = 1 UPDATE_RETURN_UPDATED = 1
}; };
enum update_progress {
UPDATE_PROGRESS_IDLE = 0,
UPDATE_PROGRESS_RUNNING = 1,
UPDATE_PROGRESS_DONE = 2
} progress;
static Directory *mp3rootDirectory; static Directory *mp3rootDirectory;
static time_t directory_dbModTime; 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 int directory_updateJobId;
static sig_atomic_t directory_updateJobId;
static DirectoryList *newDirectoryList(void); static DirectoryList *newDirectoryList(void);
...@@ -116,124 +118,66 @@ static char *getDbFile(void) ...@@ -116,124 +118,66 @@ static char *getDbFile(void)
int isUpdatingDB(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)
{
if (directory_updatePid == pid) {
update_status = status;
update_exited = 1;
wakeup_main_task();
}
} }
void readDirectoryDBIfUpdateIsFinished(void) void reap_update_task(void)
{ {
int status; if (progress != UPDATE_PROGRESS_DONE)
if (!update_exited)
return; return;
pthread_join(update_thr, NULL);
status = update_status; progress = UPDATE_PROGRESS_IDLE;
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;
} }
int updateInit(List * pathList) static void * update_task(void *arg)
{ {
if (directory_updatePid > 0) List *path_list = (List *)arg;
return 0; 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;
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) { if (path_list) {
ListNode *node = pathList->firstNode; ListNode *node = path_list->firstNode;
while (node) { while (node) {
switch (updatePath(node->key)) { switch (updatePath(node->key)) {
case UPDATE_RETURN_UPDATED: case UPDATE_RETURN_ERROR:
dbUpdated = UPDATE_RETURN_UPDATED; ret = UPDATE_RETURN_ERROR;
break; goto out;
case UPDATE_RETURN_NOUPDATE: case UPDATE_RETURN_NOUPDATE:
break; break;
case UPDATE_RETURN_ERROR: case UPDATE_RETURN_UPDATED:
exit(DIRECTORY_UPDATE_EXIT_ERROR); ret = UPDATE_RETURN_UPDATED;
} }
node = node->nextNode; node = node->nextNode;
} }
free(path_list);
} else { } else {
dbUpdated = updateDirectory(mp3rootDirectory); ret = updateDirectory(mp3rootDirectory);
if (dbUpdated == UPDATE_RETURN_ERROR)
exit(DIRECTORY_UPDATE_EXIT_ERROR);
} }
if (dbUpdated == UPDATE_RETURN_NOUPDATE) if (ret == UPDATE_RETURN_UPDATED && writeDirectoryDB() < 0)
exit(DIRECTORY_UPDATE_EXIT_NOUPDATE); ret = UPDATE_RETURN_ERROR;
out:
progress = UPDATE_PROGRESS_DONE;
wakeup_main_task();
return (void *)ret;
}
/* ignore signals since we don't want them to corrupt the db */ int updateInit(List * path_list)
ignoreSignals(); {
if (writeDirectoryDB() < 0) { pthread_attr_t attr;
exit(DIRECTORY_UPDATE_EXIT_ERROR);
} if (progress != UPDATE_PROGRESS_IDLE)
exit(DIRECTORY_UPDATE_EXIT_UPDATE);
} else if (directory_updatePid < 0) {
unblockSignals();
ERROR("updateInit: Problems forking()'ing\n");
directory_updatePid = 0;
return -1; return -1;
}
unblockSignals();
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++; directory_updateJobId++;
if (directory_updateJobId > 1 << 15) if (directory_updateJobId > 1 << 15)
directory_updateJobId = 1; 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); (int)directory_updateJobId);
return (int)directory_updateJobId; return (int)directory_updateJobId;
......
...@@ -34,7 +34,7 @@ typedef struct _Directory { ...@@ -34,7 +34,7 @@ typedef struct _Directory {
unsigned stat; /* not needed if ino_t == dev_t == 0 is impossible */ unsigned stat; /* not needed if ino_t == dev_t == 0 is impossible */
} Directory; } Directory;
void readDirectoryDBIfUpdateIsFinished(void); void reap_update_task(void);
int isUpdatingDB(void); int isUpdatingDB(void);
......
...@@ -444,7 +444,7 @@ int main(int argc, char *argv[]) ...@@ -444,7 +444,7 @@ int main(int argc, char *argv[])
COMMAND_RETURN_KILL != handlePendingSignals()) { COMMAND_RETURN_KILL != handlePendingSignals()) {
syncPlayerAndPlaylist(); syncPlayerAndPlaylist();
client_manager_expire(); client_manager_expire();
readDirectoryDBIfUpdateIsFinished(); reap_update_task();
} }
write_state_file(); write_state_file();
......
...@@ -57,7 +57,6 @@ static void chldSigHandler(mpd_unused int sig) ...@@ -57,7 +57,6 @@ static void chldSigHandler(mpd_unused int sig)
else else
break; break;
} }
directory_sigChldHandler(pid, status);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment