[Mono-list] [PATCH] robust mono-handle-d?
Dennis Haney
davh@davh.dk
11 Jun 2002 17:19:32 +0200
--=-=-=
>>>>> "Dick" == Dick Porter <dick@ximian.com> writes:
Dick> On Fri, 2002-06-07 at 22:28, Dennis Haney wrote: They're called
Dick> "file descriptors" in Linux :)
>> heh, thats the word(s) I was looking for all along ;) Shall I
>> change it? If so it will be after the commit...
Dick> Yes please.
Done and patch attached with these changes..
Dick> This change shouldnt go in. The daemon shouldn't quit if it's
Dick> still servicing clients, and it should clean up when it does
Dick> quit.
>> assert does not quit (and never has in any implementation I've
>> seen), g_assert is implemented as:
>>
>> #define g_assert(expr) G_STMT_START{ \ if (expr) { } else \ g_log
>> (G_LOG_DOMAIN, \ G_LOG_LEVEL_ERROR, \ "file %s: line %d (%s):
>> assertion failed: (%s)", \ __FILE__, \ __LINE__, \
>> __PRETTY_FUNCTION__, \ #expr); }G_STMT_END
Dick> In glib, it quits due to the call to g_log() with
Dick> G_LOG_LEVEL_ERROR severity. In the standard C library, assert()
Dick> calls abort().
Hmm.. How could I miss something so fundamentally wrong?!? :( Well at
least I can say I learned something new ;)
Dick> - Dick
--
Dennis
use Inline C => qq{void p(char*g){
printf("Just Another %s Hacker\n",g);}};p("Perl");
--=-=-=
Content-Disposition: attachment; filename=mono_handle_d_diff1.diff
Index: daemon-messages.c
===================================================================
RCS file: /mono/mono/mono/io-layer/daemon-messages.c,v
retrieving revision 1.3
diff -u -b -B -r1.3 daemon-messages.c
--- daemon-messages.c 31 May 2002 09:54:14 -0000 1.3
+++ daemon-messages.c 11 Jun 2002 15:18:14 -0000
@@ -25,7 +25,7 @@
/* Send request on fd, wait for response (called by applications, not
* the daemon)
-*/
+ */
void _wapi_daemon_request_response (int fd, WapiHandleRequest *req,
WapiHandleResponse *resp)
{
@@ -41,7 +41,7 @@
ret=send (fd, req, sizeof(WapiHandleRequest), MSG_NOSIGNAL);
if(ret!=sizeof(WapiHandleRequest)) {
if(errno==EPIPE) {
- g_warning (G_GNUC_PRETTY_FUNCTION ": The handle daemon vanished!");
+ g_critical (G_GNUC_PRETTY_FUNCTION ": The handle daemon vanished!");
exit (-1);
} else {
g_warning (G_GNUC_PRETTY_FUNCTION ": Send error: %s",
@@ -53,7 +53,7 @@
ret=recv (fd, resp, sizeof(WapiHandleResponse), MSG_NOSIGNAL);
if(ret==-1) {
if(errno==EPIPE) {
- g_warning (G_GNUC_PRETTY_FUNCTION ": The handle daemon vanished!");
+ g_critical (G_GNUC_PRETTY_FUNCTION ": The handle daemon vanished!");
exit (-1);
} else {
g_warning (G_GNUC_PRETTY_FUNCTION ": Send error: %s",
@@ -71,7 +71,9 @@
int ret;
ret=recv (fd, req, sizeof(WapiHandleRequest), MSG_NOSIGNAL);
- if(ret==-1) {
+ if(ret==-1 || ret != sizeof(WapiHandleRequest) {
+ /* make sure we dont do anything with this response */
+ req->type=WapiHandleRequestType_Error;
#ifdef DEBUG
g_warning (G_GNUC_PRETTY_FUNCTION ": Recv error: %s",
strerror (errno));
@@ -86,11 +88,11 @@
int ret;
ret=send (fd, resp, sizeof(WapiHandleResponse), MSG_NOSIGNAL);
- if(ret==-1) {
#ifdef DEBUG
+ if(ret==-1 || ret != sizeof(WapiHandleResponse)) {
g_warning (G_GNUC_PRETTY_FUNCTION ": Send error: %s",
strerror (errno));
-#endif
/* The next loop around poll() should tidy up */
}
+#endif
}
Index: daemon-messages.h
===================================================================
RCS file: /mono/mono/mono/io-layer/daemon-messages.h,v
retrieving revision 1.2
diff -u -b -B -r1.2 daemon-messages.h
--- daemon-messages.h 9 May 2002 13:10:18 -0000 1.2
+++ daemon-messages.h 11 Jun 2002 15:18:14 -0000
@@ -13,6 +13,7 @@
#include <mono/io-layer/wapi-private.h>
typedef enum {
+ WapiHandleRequestType_Error,
WapiHandleRequestType_New,
WapiHandleRequestType_Open,
WapiHandleRequestType_Close,
Index: daemon.c
===================================================================
RCS file: /mono/mono/mono/io-layer/daemon.c,v
retrieving revision 1.4
diff -u -b -B -r1.4 daemon.c
--- daemon.c 31 May 2002 10:41:30 -0000 1.4
+++ daemon.c 11 Jun 2002 15:18:14 -0000
@@ -27,9 +27,13 @@
#undef DEBUG
+/* Array to hold the filedescr. we are currently polling */
static struct pollfd *pollfds=NULL;
+/* Keep track of the size and usage of pollfds */
static int nfds=0, maxfds=0;
-static gpointer *handle_refs=NULL;
+/* Array to keep track of arrays of handles that belong to a given client */
+static guint32 **handle_refs=NULL;
+/* The sockey which we listen to new connections on */
static int main_sock;
/* Deletes the shared memory segment. If we're exiting on error,
@@ -40,12 +44,23 @@
_wapi_shm_destroy ();
}
+/*
+ * signal_handler:
+ * @unused: unused
+ *
+ * Called if daemon receives a SIGTERM or SIGINT
+ */
static void signal_handler (int unused)
{
cleanup ();
exit (-1);
}
+/*
+ * startup:
+ *
+ * Bind signals and attach to shared memory
+ */
static void startup (void)
{
struct sigaction sa;
@@ -76,6 +91,15 @@
time (NULL));
}
+
+/*
+ * ref_handle:
+ * @idx: idx into pollfds
+ * @handle: handle to inc refcnt
+ *
+ * Increase ref count of handle for idx's filedescr. and the
+ * shm. Handle 0 is ignored.
+ */
static void ref_handle (guint32 idx, guint32 handle)
{
guint32 *open_handles=handle_refs[idx];
@@ -95,6 +119,15 @@
#endif
}
+/*
+ * unref_handle:
+ * @idx: idx into pollfds
+ * @handle: handle to inc refcnt
+ *
+ * Decrease ref count of handle for idx's filedescr. and the shm. If
+ * global ref count reaches 0 it is free'ed. Return TRUE if the local
+ * ref count is 0. Handle 0 is ignored.
+ */
static gboolean unref_handle (guint32 idx, guint32 handle)
{
guint32 *open_handles=handle_refs[idx];
@@ -104,6 +137,13 @@
return(FALSE);
}
+ if (open_handles[handle] == 0) {
+ g_warning(G_GNUC_PRETTY_FUNCTION
+ ": unref on %d called when ref was already 0",
+ handle);
+ return TRUE;
+ }
+
_wapi_shared_data->handles[handle].ref--;
open_handles[handle]--;
@@ -140,13 +180,21 @@
return(destroy);
}
+/*
+ * add_fd:
+ * @fd: Filehandle to add
+ *
+ * Add filedescriptor to the pollfds array, expand if necessary
+ */
static void add_fd(int fd)
{
if(nfds==maxfds) {
/* extend the array */
maxfds+=10;
+ /* no need to memset the extra memory, we init it
+ * before use anyway */
pollfds=g_renew (struct pollfd, pollfds, maxfds);
- handle_refs=g_renew (gpointer, handle_refs, maxfds);
+ handle_refs=g_renew (guint32*, handle_refs, maxfds);
}
pollfds[nfds].fd=fd;
@@ -158,6 +206,14 @@
nfds++;
}
+/*
+ * rem_fd:
+ * @idx: idx into pollfds to remove
+ *
+ * Close filedescriptor and remove it from the pollfds array. Closes
+ * all handles that it may have open. If only main_sock is open, the
+ * daemon is shut down.
+ */
static void rem_fd(int idx)
{
guint32 *open_handles=handle_refs[idx], handle_count;
@@ -205,12 +261,28 @@
}
}
+
+/*
+ * send_reply:
+ * @idx: idx into pollfds.
+ * @rest: Package to send
+ *
+ * Send a package to a client
+ */
static void send_reply (guint32 idx, WapiHandleResponse *resp)
{
/* send message */
_wapi_daemon_response (pollfds[idx].fd, resp);
}
+/*
+ * process_new:
+ * @idx: idx into pollfds.
+ * @type: type to init handle to
+ *
+ * Find a free handle and initialize it to 'type', increase refcnt and
+ * send back a reply to the client.
+ */
static void process_new (guint32 idx, WapiHandleType type)
{
guint32 handle;
@@ -231,6 +303,14 @@
send_reply (idx, &resp);
}
+/*
+ * process_open:
+ * @idx: idx into pollfds.
+ * @handle: handle no.
+ *
+ * Increase refcnt on a previously created handle and send back a
+ * response to the client.
+ */
static void process_open (guint32 idx, guint32 handle)
{
WapiHandleResponse resp;
@@ -259,6 +339,14 @@
send_reply (idx, &resp);
}
+/*
+ * process_close:
+ * @idx: idx into pollfds.
+ * @handle: handle no.
+ *
+ * Decrease refcnt on a previously created handle and send back a
+ * response to the client with notice of it being destroyed.
+ */
static void process_close (guint32 idx, guint32 handle)
{
WapiHandleResponse resp;
@@ -273,6 +361,13 @@
send_reply (idx, &resp);
}
+/*
+ * process_scratch:
+ * @idx: idx into pollfds.
+ * @length: allocate this much scratch space
+ *
+ * Allocate some scratch space and send a reply to the client.
+ */
static void process_scratch (guint32 idx, guint32 length)
{
WapiHandleResponse resp;
@@ -288,6 +383,13 @@
send_reply (idx, &resp);
}
+/*
+ * process_scratch_free:
+ * @idx: idx into pollfds.
+ * @scratch_idx: deallocate this scratch space
+ *
+ * Deallocate scratch space and send a reply to the client.
+ */
static void process_scratch_free (guint32 idx, guint32 scratch_idx)
{
WapiHandleResponse resp;
@@ -303,6 +405,13 @@
send_reply (idx, &resp);
}
+/*
+ * read_message:
+ * @idx: idx into pollfds.
+ *
+ * Read a message (A WapiHandleRequest) from a client and dispatch
+ * whatever it wants to the process_* calls.
+ */
static void read_message (guint32 idx)
{
WapiHandleRequest req;
@@ -314,9 +423,15 @@
process_new (idx, req.u.new.type);
break;
case WapiHandleRequestType_Open:
+#ifdef DEBUG
+ g_assert(req.u.open.handle < _WAPI_MAX_HANDLES);
+#endif
process_open (idx, req.u.open.handle);
break;
case WapiHandleRequestType_Close:
+#ifdef DEBUG
+ g_assert(req.u.close.handle < _WAPI_MAX_HANDLES);
+#endif
process_close (idx, req.u.close.handle);
break;
case WapiHandleRequestType_Scratch:
@@ -325,9 +440,19 @@
case WapiHandleRequestType_ScratchFree:
process_scratch_free (idx, req.u.scratch_free.idx);
break;
+ case WapiHandleRequestType_Error: /* Falltrough */
+ default: /* Catch faulty packages */
+ /*FIXME: call rem_fd?*/
+ break;
}
}
+/*
+ * main:
+ *
+ * Open socket, create shared mem segment and begin listening for
+ * clients.
+ */
int main(int argc, char **argv)
{
struct sockaddr_un main_socket_address;
@@ -347,8 +472,8 @@
ret=bind(main_sock, (struct sockaddr *)&main_socket_address,
sizeof(struct sockaddr_un));
if(ret==-1) {
- g_warning ("bind failed: %s", strerror (errno));
- _wapi_shared_data->daemon_running=2;
+ g_critical ("bind failed: %s", strerror (errno));
+ _wapi_shared_data->daemon_running=DAEMON_DIED_AT_STARTUP;
exit(-1);
}
@@ -358,8 +483,8 @@
ret=listen(main_sock, 5);
if(ret==-1) {
- g_warning ("listen failed: %s", strerror (errno));
- _wapi_shared_data->daemon_running=2;
+ g_critical ("listen failed: %s", strerror (errno));
+ _wapi_shared_data->daemon_running=DAEMON_DIED_AT_STARTUP;
exit(-1);
}
@@ -373,7 +498,7 @@
* ready. From now on, it's up to us to delete the shared
* memory segment when appropriate.
*/
- _wapi_shared_data->daemon_running=1;
+ _wapi_shared_data->daemon_running=DAEMON_RUNNING;
while(TRUE) {
int i;
@@ -385,21 +510,29 @@
/* Block until something happens */
ret=poll(pollfds, nfds, -1);
if(ret==-1) {
- g_message ("poll error: %s", strerror (errno));
+ g_critical ("poll error: %s", strerror (errno));
cleanup ();
exit(-1);
}
for(i=0; i<nfds; i++) {
- if(((pollfds[i].revents&POLLHUP)==POLLHUP) ||
- ((pollfds[i].revents&POLLERR)==POLLERR) ||
- ((pollfds[i].revents&POLLNVAL)==POLLNVAL)) {
+ if(((pollfds[i].revents & POLLHUP) == POLLHUP) ||
+ ((pollfds[i].revents & POLLERR) == POLLERR) ||
+ ((pollfds[i].revents & POLLNVAL) == POLLNVAL)) {
#ifdef DEBUG
g_message ("fd[%d] %d error", i,
pollfds[i].fd);
#endif
rem_fd(i);
} else if((pollfds[i].revents&POLLIN)==POLLIN) {
+ /* If a client is connecting, accept
+ * it and begin listening to that
+ * socket too otherwise it must be a
+ * client we already have that wants
+ * something
+ */
+
+ /* FIXME: Isn't this only true for i==0??? */
if(pollfds[i].fd==main_sock) {
int newsock;
struct sockaddr addr;
@@ -407,7 +540,7 @@
newsock=accept(main_sock, &addr,
&addrlen);
if(newsock==-1) {
- g_message("accept error: %s",
+ g_critical("accept error: %s",
strerror (errno));
cleanup ();
exit(-1);
Index: handles.c
===================================================================
RCS file: /mono/mono/mono/io-layer/handles.c,v
retrieving revision 1.13
diff -u -b -B -r1.13 handles.c
--- handles.c 8 Jun 2002 17:57:49 -0000 1.13
+++ handles.c 11 Jun 2002 15:18:14 -0000
@@ -135,6 +135,13 @@
thread_handle=_wapi_handle_new (WAPI_HANDLE_THREAD);
}
+/*
+ * _wapi_handle_new_internal:
+ * @type: Init handle to this type
+ *
+ * Search for a free handle and initialize it. Return the handle on
+ * succes and 0 on failure.
+ */
guint32 _wapi_handle_new_internal (WapiHandleType type)
{
guint32 i;
@@ -149,7 +156,7 @@
struct _WapiHandleShared *shared=&_wapi_shared_data->handles[i];
if(shared->type==WAPI_HANDLE_UNUSED) {
- last=i;
+ last=i+1;
shared->type=type;
shared->signalled=FALSE;
mono_mutex_init (&_wapi_shared_data->handles[i].signal_mutex, &mutex_shared_attr);
@@ -163,7 +170,7 @@
/* Try again from the beginning */
last=1;
goto again;
- }
+}
return(0);
@@ -350,6 +357,13 @@
#define HDRSIZE sizeof(struct _WapiScratchHeader)
+/*
+ * _wapi_handle_scratch_store_internal:
+ * @bytes: Allocate no. bytes
+ *
+ * Like malloc(3) except its for the shared memory segment's scratch
+ * part. Memory block returned is zeroed out.
+ */
guint32 _wapi_handle_scratch_store_internal (guint32 bytes)
{
guint32 idx=0, last_idx=0;
@@ -400,6 +414,10 @@
g_message (G_GNUC_PRETTY_FUNCTION ": new header at %d, length %d", idx+bytes, hdr->length);
#endif
+ /*
+ * It was memset(0..) when free/made so no need to do it here
+ */
+
return(idx);
} else if(hdr->flags & WAPI_SHM_SCRATCH_FREE &&
last_was_free == FALSE) {
@@ -516,6 +534,13 @@
return(str);
}
+/*
+ * _wapi_handle_scratch_delete_internal:
+ * @idx: Index to free block
+ *
+ * Like free(3) except its for the shared memory segment's scratch
+ * part.
+ */
void _wapi_handle_scratch_delete_internal (guint32 idx)
{
struct _WapiScratchHeader *hdr;
Index: shared.c
===================================================================
RCS file: /mono/mono/mono/io-layer/shared.c,v
retrieving revision 1.4
diff -u -b -B -r1.4 shared.c
--- shared.c 9 May 2002 13:10:18 -0000 1.4
+++ shared.c 11 Jun 2002 15:18:14 -0000
@@ -56,6 +56,17 @@
#undef DEBUG
+/*
+ * _wapi_shm_attach:
+ * @daemon: Is it the daemon trying to attach to the segment
+ * @success: Was it a success
+ * @shm_id: The ID of the segment created/attached to
+ *
+ * Attach to the shared memory segment or create it if it did not
+ * exist. If it was created and daemon was FALSE a new daemon is
+ * forked into existence. Returns the memory area the segment was
+ * attached to.
+ */
gpointer _wapi_shm_attach (gboolean daemon, gboolean *success, int *shm_id)
{
gpointer shm_seg;
@@ -99,7 +110,7 @@
*/
} else {
/* Some error other than EEXIST */
- g_message (G_GNUC_PRETTY_FUNCTION ": shmget error: %s",
+ g_critical (G_GNUC_PRETTY_FUNCTION ": shmget error: %s",
strerror (errno));
exit (-1);
}
@@ -110,7 +121,7 @@
*/
shm_seg=shmat (*shm_id, NULL, 0);
if(shm_seg==(gpointer)-1) {
- g_message (G_GNUC_PRETTY_FUNCTION ": shmat error: %s",
+ g_critical (G_GNUC_PRETTY_FUNCTION ": shmat error: %s",
strerror (errno));
if(fork_daemon==TRUE) {
_wapi_shm_destroy ();
@@ -131,7 +142,7 @@
pid=fork ();
if(pid==-1) {
- g_message (G_GNUC_PRETTY_FUNCTION ": fork error: %s",
+ g_critical (G_GNUC_PRETTY_FUNCTION ": fork error: %s",
strerror (errno));
_wapi_shm_destroy ();
exit (-1);
@@ -140,9 +151,9 @@
setsid ();
execl (MONO_BINDIR "/mono-handle-d", "mono-handle-d",
NULL);
- g_warning (": exec of %s/mono-handle-d failed: %s",
+ g_critical (": exec of %s/mono-handle-d failed: %s",
MONO_BINDIR, strerror (errno));
- data->daemon_running=2;
+ data->daemon_running=DAEMON_DIED_AT_STARTUP;
exit (-1);
}
/* parent carries on */
@@ -150,8 +161,9 @@
/* Do some sanity checking on the shared memory we
* attached
*/
- if(!(data->daemon_running==0 || data->daemon_running==1 ||
- data->daemon_running==2) ||
+ if(!(data->daemon_running==DAEMON_STARTING ||
+ data->daemon_running==DAEMON_RUNNING ||
+ data->daemon_running==DAEMON_DIED_AT_STARTUP) ||
(strncmp (data->daemon+1, "mono-handle-daemon-", 19)!=0)) {
g_warning ("Shared memory sanity check failed.");
*success=FALSE;
@@ -159,7 +171,7 @@
}
}
- for(tries=0; data->daemon_running==0 && tries < 100; tries++) {
+ for(tries=0; data->daemon_running==DAEMON_STARTING && tries < 100; tries++) {
/* wait for the daemon to sort itself out. To be
* completely safe, we should have a timeout before
* giving up.
@@ -171,7 +183,7 @@
nanosleep (&sleepytime, NULL);
}
- if(tries==100 && data->daemon_running==0) {
+ if(tries==100 && data->daemon_running==DAEMON_STARTING) {
/* Daemon didnt get going */
if(fork_daemon==TRUE) {
_wapi_shm_destroy ();
@@ -181,7 +193,7 @@
return(NULL);
}
- if(data->daemon_running==2) {
+ if(data->daemon_running==DAEMON_DIED_AT_STARTUP) {
/* Oh dear, the daemon had an error starting up */
if(fork_daemon==TRUE) {
_wapi_shm_destroy ();
Index: wapi-private.h
===================================================================
RCS file: /mono/mono/mono/io-layer/wapi-private.h,v
retrieving revision 1.12
diff -u -b -B -r1.12 wapi-private.h
--- wapi-private.h 9 May 2002 13:10:19 -0000 1.12
+++ wapi-private.h 11 Jun 2002 15:18:14 -0000
@@ -95,6 +95,12 @@
#define _WAPI_MAX_HANDLES 4096
#define _WAPI_HANDLE_INVALID (gpointer)-1
+typedef enum {
+ DAEMON_STARTING = 0,
+ DAEMON_RUNNING = 1,
+ DAEMON_DIED_AT_STARTUP = 2,
+} _wapi_daemon_status;
+
/*
* This is the layout of the shared memory segment
*/
@@ -104,7 +110,7 @@
* header file
*/
guchar daemon[108];
- guint32 daemon_running;
+ _wapi_daemon_status daemon_running;
#ifdef _POSIX_THREAD_PROCESS_SHARED
mono_mutex_t signal_mutex;
--- /dev/null Thu Jan 1 01:00:00 1970
+++ docs/.cvsignore Sun Jun 2 00:00:05 2002
@@ -0,0 +1 @@
+Makefile Makefile.in
--- /dev/null Thu Jan 1 01:00:00 1970
+++ docs/mono_handle_d Tue Jun 11 17:17:18 2002
@@ -0,0 +1,98 @@
+=pod
+
+=head1 Internal design document for the mono_handle_d
+
+This document is designed to hold the design of the mono_handle_d and
+not as an api reference.
+
+=head2 Primary goal and purpose
+
+The mono_handle_d is a process which takes care of the (de)allocation
+of scratch shared memory and handles (of files, threads, mutexes,
+sockets etc. see L<WapiHandleType>) and refcounts of the
+filehandles. It is designed to be run by a user and to be fast, thus
+minimal error checking on input is done and will most likely crash if
+given a faulty package. No effort has been, or should be, made to have
+the daemon talking to machine of different endianness/size of int.
+
+=head2 How to start the daemon
+
+To start the daemon you either run the mono_handle_d executable or try
+to attach to the shared memory segment via L<_wapi_shm_attach> which
+will start a daemon if one does not exist.
+
+=head1 Internal details
+
+The daemon works by opening a socket and listening to clients. These
+clients send packages over the socket complying to L<struct
+WapiHandleRequest>.
+
+=head2 Possible requests
+
+=over
+
+=item WapiHandleRequest_New
+
+Find a handle in the shared memory segment that is free and allocate
+it to the specified type. To destroy use
+L</WapiHandleRequest_Close>. A L<WapiHandleResponse> with
+.type=WapiHandleResponseType_New will be sent back with .u.new.handle
+set to the handle that was allocated. .u.new.type is the type that was
+requested.
+
+=item WapiHandleRequestType_Open
+
+Increase the ref count of an already created handle. A
+L<WapiHandleResponse> with .type=WapiHandleResponseType_Open will be sent
+back with .u.new.handle set to the handle, .u.new.type is set to the
+type of handle this is.
+
+=item WapiHandleRequestType_Close
+
+Decrease the ref count of an already created handle. A
+L<WapiHandleResponse> with .type=WapiHandleResponseType_Close will be
+sent back with .u.close.destroy set to TRUE if ref count for this
+client reached 0.
+
+=item WapiHandleRequestType_Scratch
+
+Allocate a shared memory area of size .u.scratch.length in bytes. A
+L<WapiHandleResponse> with .type=WapiHandleResponseType_Scratch will be
+sent back with .u.scratch.idx set to the index into the shared
+memory's scratch area where to memory begins. (works just like
+malloc(3))
+
+=item WapiHandleRequestType_Scratch
+
+Deallocate a shared memory area, this must have been allocated before
+deallocating. A L<WapiHandleResponse> with
+.type=WapiHandleResponseType_ScratchFree will be sent back (works just
+like free(3))
+
+=back
+
+=head1 Why a daemon
+
+From an email:
+
+Dennis: I just have one question about the daemon... Why does it
+exist? Isn't it better performancewise to just protect the shared area
+with a mutex when allocation a new handle/shared mem segment or
+changing refcnt? It will however be a less resilient to clients that
+crash (the deamon cleans up ref'd handles if socket closes)
+
+Dick: It's precisely because with a mutex the shared memory segment
+can be left in a locked state. Also, it's not so easy to clean up
+shared memory without it (you can't just mark it deleted when creating
+it, because you can't attach any more readers to the same segment
+after that). I did some minimal performance testing, and I don't
+think the daemon is particularly slow.
+
+
+=head1 Authors
+
+Documentaion: Dennis Haney
+
+Implementation: Dick Porter
+
+=cut
--=-=-=--