# # Patch name: asynchronous queue # Patch version: alpha # Author's name: Codebot Unit 5 # Author's email: anonycow@yahoo.com # Version of PennMUSH: 1.7.4p15 # Date patch made: 2002 March 15 # Author is willing to support (yes/no): no # Patch format: diff -c # # # This is a contributed PennMUSH patch. Its use is subject to the # same restrictions found in PennMUSH's hdrs/copyrite.h file. # # No warranty is given for this patch. It is not necessarily going # to work on your system, with any version of PennMUSH other than # the one above, etc. # # If the author given above was willing to support the patch, you # should write to the author if you have any questions or problems. Do # *NOT* send email messages to Javelin or any PennMUSH mailing list about # this patch! # # Below this line is the author's description of the patch, # followed by the patch itself. If the patch is in context diff # format, you'll probably apply it by typing: patch < patchfile # in your top-level MUSH directory, unless instructed otherwise # below. # # # REQUIREMENTS # # This patch is targetted at developers. It provides nothing useful to anyone # not hacking the source, although this patch could be a dependency for other # patches down the line. Since this is version alpha, I have no idea of how to # gauge that. You should optionally be familiar with how to compile with POSIX # threads (pthreads) on your system. # # # DESCRIPTION # # For lack of a better name, I'm calling this an "asynchronous queue" patch. # While not quite strictly true, I think it sounds more professional than, # "whacked triggerable event queue thingy." # # The basic idea is something like, "semaphores for hardcode." # # Using the mechanisms provided by this patch, you can queue a command to be # executed whenever your hardcode finishes a task. For example, you could # write something to a network socket, then let the MUSH continue processing. # Each second, using local_timer, you could check the status of the network to # see if you got a response. If you didn't, you can wait some more. If you # did, you can copy some data into the asynchronous queue item, and have it # execute. For example, I coded an interface to Lynx using this patch. # Another potential use would be to implement non-blocking SQL queries. # # This patch is even more useful with POSIX threads. Alas, getting POSIX # threads working is a bit of a chore the first time around for the novice. # And while POSIX thread emulation libraries exist for Windows, this patch is # not targetted at, nor tested on, Windows installations. # # # ADDED BY THIS PATCH # # Hardcode: # - async_queue # - async_complete # - async_monitor (optional) # # # MODIFIED BY THIS PATCH # # Softcode: # - @ps (can view asynchronous queue) # - @halt (can halt asynchronous queue items) # # Files: # - cque.c (adds async_queue, async_complete and modifies do_queue, do_halt) # - bsd.c (adds POSIX thread support--optionally compiled) # # # INSTALLATION # # Apply by typing in your pennmush directory: patch -p0 < async.174p15 # # Then modify your top-level Makefile by adding -DCU5_ASYNC to CCFLAGS. # Alternately, you can put #define CU5_ASYNC in your options.h. If you use the # first method, you'll want to do a make clean, then a make install, or you can # alternately touch src/bsd.c and touch src/cque.c. # # # POSIX THREADS (optional) # # The asynchronous patch is more useful (or depending on your opinion, more # useless) if you enable POSIX thread support. The steps needed to compile in # POSIX thread support vary wildly from system to system. There is usually a # compile time option like -pthread or -pthreads, and you can also manually # include all the proper libraries and preprocessor definitions. It's not for # the faint of heart. # # For example, since PennMUSH was linking explicitly with libc on my Linux # system (the -lc option in CLIBS), my netmush was being linked with the thread # unsafe version of several functions, leading to strange and inexplicable # crashes. The solution was to remove -lc from CLIBS. Alternately, I could # have included -lpthread before -lc. And debugging with threads is always a # hassle. # # Anyway, after you figure that out, you'll need to add -DCU5_HAS_PTHREADS, or # put #define CU5_HAS_PTHREADS in your options.h. If you use the first method, # you'll want to do a make clean, then a make install, or you can alternately # touch src/bsd.c. # # # PROGRAMMING # # - BQUE *async_queue(player, cause, command) # # Queues asynchronous item. Returns a BQUE item. BQUE is an opaque type. # Declare it as typedef struct bque BQUE. Definition is in cque.c if you # need it. Pass the BQUE item to async_complete when you want the item to # be executed in the next queue cycle. # # - void async_complete(BQUE *qptr) # # Puts the item referenced by qptr on the command queue to be executed during # the next queue cycle. # # - pthread_mutex_t async_monitor (optional) # # This mutex is always locked by the main PennMUSH thread, except when # waiting for network traffic in select(). Since PennMUSH was never designed # to be threaded, this preserves serialization of calls to PennMUSH functions # as long as you're careful to always acquire async_monitor before making any # calls to PennMUSH functions or make modifications to global data. Be sure # to release async_monitor as quickly as possible, or otherwise the # processing of network traffic by the server will be blocked. # # A good strategy is to complete as much work in a thread as possible, and # then acquire the async_monitor just to make a few PennMUSH calls. # # # SHAMELESS PLUG # # Visit feem.dyndns.org 1978! My character there is CU5. # Index: src/bsd.c diff -c -r1.1 -r1.2 *** src/bsd.c 2002/03/11 21:54:06 1.1 --- src/bsd.c 2002/03/12 02:42:08 1.2 *************** *** 128,133 **** --- 128,138 ---- #include "game.h" #include "confmagic.h" + #if defined(CU5_ASYNC) && defined(CU5_HAS_PTHREADS) + #include + pthread_mutex_t async_monitor = PTHREAD_MUTEX_INITIALIZER; + #endif /* CU5_ASYNC and CU5_HAS_PTHREADS */ + /* Define to enable experimental conversion of accented characters to HTML entities for Pueblo. Meant for European character sets */ #define PUEBLO_TRANSLATE *************** *** 2112,2119 **** --- 2117,2130 ---- FD_SET(d->descriptor, &output_set); } + #if defined(CU5_ASYNC) && defined(CU5_HAS_PTHREADS) + pthread_mutex_unlock(&async_monitor); + #endif /* CU5_ASYNC and CU5_HAS_PTHREADS */ if ((found = select(maxd, &input_set, &output_set, (fd_set *) 0, &timeout)) < 0) { + #if defined(CU5_ASYNC) && defined(CU5_HAS_PTHREADS) + pthread_mutex_lock(&async_monitor); + #endif /* CU5_ASYNC and CU5_HAS_PTHREADS */ #ifdef WIN32 if (found == SOCKET_ERROR && WSAGetLastError() != WSAEINTR) #else *************** *** 2134,2139 **** --- 2145,2153 ---- } #endif } else { + #if defined(CU5_ASYNC) && defined(CU5_HAS_PTHREADS) + pthread_mutex_lock(&async_monitor); + #endif /* CU5_ASYNC and CU5_HAS_PTHREADS */ /* if !found then time for robot commands */ if (!found) { Index: src/cque.c diff -c -r1.1 cque.c *** src/cque.c 2002/03/11 21:54:06 1.1 --- src/cque.c 2002/03/17 17:38:55 *************** *** 387,392 **** --- 387,492 ---- } } + #ifdef CU5_ASYNC + + static BQUE *qasync = NULL; /* asynchronous queue */ + static BQUE **qasyncnext = &qasync; /* pointer to end of queue */ + + /* Add a command to the asynchronous queue. The asynchronous queue keeps track + of asynchronous events which have yet to be completed. Such events can be + completed by calling async_complete() with the queue item's pointer. + + Arguments: + player Dbref of executor. + cause Dbref of enactor. + command String of command to be executed. + + Returns: Pointer to queue item. Only use to pass to async_complete(). + + CAVEAT: While asynchronous queue items can be viewed with @ps, they can not + be @kick'd, @notify'd, or @drain'd, although they can be @halt'd. + + NOTE: As currently implemented, synchronization is provided by a monitor + around the select() in bsd.c, when the server is simply waiting on network + I/O. Worker threads should acquire and release the monitor mutex + 'async_monitor' before and after any PennMUSH calls which modify global or + shared data. This is just about every PennMUSH call, although there are a + few exceptions. + + NOTE: Server response time to network I/O can degrade if the monitor is held + for extended periods, so only acquire the monitor mutex to briefly transfer + data to and from PennMUSH. This is hopefully not a problem as these + asynchronous facilities are mainly useful for extended or unpredictable + computations which can be carried out independently of PennMUSH and with + independent synchronization mechanisms. */ + BQUE * + async_queue(player, cause, command) + dbref player; + dbref cause; + char *command; + { + BQUE *tmp; + int a; + + /* Make sure player can afford to do it. */ + if (!pay_queue(player, command)) + return NULL; + + /* Allocate queue item. */ + if (!(tmp = (BQUE *) mush_malloc(sizeof(BQUE), "BQUE"))) + return NULL; + if (!(tmp->comm = mush_strdup(command, "bqueue_comm"))) { + mush_free(tmp, "BQUE"); + return NULL; + } + + /* Configure queue item. */ + tmp->player = player; + tmp->queued = QUEUE_PER_OWNER ? Owner(player) : player; + tmp->cause = cause; + tmp->semattr = NULL; + tmp->left = 0; + tmp->next = NULL; + + /* Save arguments. */ + for (a = 0; a < 10; a++) + tmp->env[a] = (wenv[a]) ? mush_strdup(wenv[a], "bqueue_env") : NULL; + + /* Save registers. */ + for (a = 0; a < NUMQ; a++) + tmp->rval[a] = (renv[a] && *renv[a]) ? mush_strdup(renv[a], "bqueue_rval") : NULL; + + /* Append to the asynchronous queue. */ + *qasyncnext = tmp; + qasyncnext = &tmp->next; + return tmp; + } + + /* Completes queuing of an item added by async_queue. */ + void + async_complete(BQUE *qptr) + { + BQUE **qnext; + + /* Make sure the item is in the queue. */ + for (qnext = &qasync; *qnext; qnext = &(*qnext)->next) { + if (*qnext == qptr) { + /* Leave from asynchronous queue. */ + if (!qptr->next) qasyncnext = qnext; + *qnext = qptr->next; + qptr->next = NULL; + + /* Enter onto execution queue. */ + if (IsPlayer(qptr->cause)) + qlast = (qlast ? qlast->next : qfirst) = qptr; + else + qllast = (qllast ? qllast->next : qlfirst) = qptr; + return; + } + } + } + #endif /* CU5_ASYNC */ + void do_second() { *************** *** 828,833 **** --- 928,936 ---- int dpq = 0, doq = 0, dwq = 0, dsq = 0; int pq = 0, oq = 0, wq = 0, sq = 0; int tpq = 0, toq = 0, twq = 0, tsq = 0; + #ifdef CU5_ASYNC + int daq = 0, aq = 0, taq = 0; + #endif /* CU5_ASYNC */ if (flag == 2 || flag == 3) quick = 1; if (flag == 1 || flag == 2) { *************** *** 872,882 **** --- 975,993 ---- if (!quick) notify(player, T("Semaphore Queue:")); show_queue(player, victim, 2, quick, all, qsemfirst, &tsq, &sq, &dsq); + #ifdef CU5_ASYNC if (!quick) + notify(player, T("Asynchronous Queue:")); + show_queue(player, victim, 0, quick, all, qasync, &taq, &aq, &daq); + #endif /* CU5_ASYNC */ + if (!quick) notify(player, T("------------ Queue Done ------------")); notify_format(player, "Totals: Player...%d/%d[%ddel] Object...%d/%d[%ddel] Wait...%d/%d Semaphore...%d/%d", pq, tpq, dpq, oq, toq, doq, wq, twq, sq, tsq); + #ifdef CU5_ASYNC + notify_format(player, " Asynchronous...%d/%d", aq, taq); + #endif /* CU5_ASYNC */ } } *************** *** 944,949 **** --- 1055,1076 ---- } else next = (trail = point)->next; } + + #ifdef CU5_ASYNC + /* clear asynchronous queue */ + for (point = qasync; point; point = next) { + next = point->next; + if (qasync == point) qasync = next; + + if ((point->player == player) || (Owner(point->player) == player)) { + num--; + giveto(player, QUEUE_COST); + free_qentry(point); + } + } + + if (!qasync) qasyncnext = &qasync; + #endif /* CU5_ASYNC */ add_to(QUEUE_PER_OWNER ? Owner(player) : player, num);