#include "brttutil.h" Pmtmanagedfifo * pmtmanagedfifo_create (char *name) Pmtmanagedfifo * pmtmanagedfifo_get (char *name) Pmtfifo * pmtmanagedfifo_set_consumer (Pmtmanagedfifo *mtf, char *consumer_name, int maxqueue, int block) Pmtfifo * pmtmanagedfifo_get_consumer (Pmtmanagedfifo *mtf, char *consumer_name) int pmtmanagedfifo_set_producer (Pmtmanagedfifo *mtf, char *producer_name) int pmtmanagedfifo_destroy (Pmtmanagedfifo *mtf) int pmtmanagedfifo_push (Pmtmanagedfifo *mtf, Hook *data, Hook *(*copyproc) (Hook *), int copydata) int pmtmanagedfifo_push_eof (Pmtmanagedfifo *mtf, char *producer_name) int pmtmanagedfifo_pop (Pmtmanagedfifo *mtf, char *consumer_name, Hook **data, unsigned long *sequence) int pmtmanagedfifo_display (char *pre, Pmtmanagedfifo *mtf)
These define an MT-safe interface to a FIFO queue of data objects. This particular version is implemented using the pmtfifo(3) Posix MT fifo routines. What makes these routines different from the pmtfifo(3) routines is that these routines include memory management of data objects whose pointers appear in the fifo queues. Also, the pmtmanagedfifo(3) routines implement a feature where multiple consumers can independently read, or pop, from the same fifo queue.
pmtmanagedfifo_create will create a Pmtmanagedfifo object and return a pointer to the object, or NULL if there is an error. The name of the new managed fifo queue is specified by name. Fifo queue names must be unique.
pmtmanagedfifo_get will return the Pmtmanagedfifo object pointer corresponding to a managed fifo queue with name name or NULL if there is no such managed fifo object.
pmtmanagedfifo_set_consumer will set a single consumer for the managed fifo queue specified by mtf, with the consumer name consumer_name. Consumer names must be unique within a single managed fifo queue. Multiple consumers are specified by calling pmtmanagedfifo_set_consumer multiple times. maxqueue controls the blocking behavior of pushes and is the maximum allowed size of the FIFO queue before blocking occurs on the push. If maxqueue is set to 0, then the FIFO will grow indefinitely and pushes will never block. block controls the blocking behavior of reads, or pops, for this consumer and if set, then will cause pops to block when the FIFO is empty until the FIFO is non-empty. The Pmtfifo object pointer for this consumer is returned or NULL if there is an error.
pmtmanagedfifo_get_consumer will return the Pmtfifo object pointer corresponding to a managed fifo queue consumer with name consumer_name or NULL if there is no such consumer.
pmtmanagedfifo_set_producer will set a single producer for the managed fifo queue specified by mtf, with the producer name producer_name. Producer names must be unique within a single managed fifo queue. Multiple producers are specified by calling pmtmanagedfifo_set_producer multiple times. This call is used in conjunction with the pmtmanagedfifo_push_eof and pmtmanagedfifo_pop calls to provide a mechanism for indicating end-of-processing in queues that have multiple producers.
pmtmanagedfifo_destroy will free all resources associated with a managed fifo queue specified by mtf and return 0, or -1 if an error occurs.
pmtmanagedfifo_push will push a new data object onto the head of the managed fifo queue specified by mtf and return 0, or -1 if an error occurs. The input data object is referenced through the standard Antelope Hook pointer, data. See hook(3) for a description of generic opaque hook pointers. Hooks provide a generalized way for packaging a pointer to a data structure with a callback proceedure for freeing that data structure. It is important for the hookfree callback to be properly specified when the hook is created in the calling routines with new_hook(3). The managed fifo routines use this feature of hooks to free up data objects when they are no longer needed; a simple garbage collection scheme. Copies of the same data objects are provided to the different consumers and therefore the managed fifo routines need to know how to safely make copies of data objects. This is done throught the copyproc callback which returns a Hook pointer of the copied object. Since data object copying happens automatically within the managed fifo routines, this callback pointer must be properly specified; it is not acceptable to set this to NULL. If the caller needs a particular data object to be copied before it is pushed onto the fifo queues, then the copydata flag should be set. Otherwise, the data pointer is placed directly into the queues and subsequent memory management of this data object becomes the respnosibility of the managed fifo routines.
pmtmanagedfifo_push_eof will push an end of file token unto the head of the managed fifo queue specified by mtf and return 0, or -1 if an error occurs. If producer_name is non-NULL and corresponds to a registered producer, through calls to pmtmanagedfifo_set_producer, then the EOF token is not actually pushed unto the queue until all of the registered producers have indicated end-of-processing using this call. This keeps the EOF token from appearing in the pmtmanagedfifo_pop return until all of the producers are finished.
pmtmanagedfifo_pop will pop the oldest pointer from the tail of the managed fifo queue referenced by mtf for the consumer with name consumer_name, returning it in *data as a hook pointer. The return values are the same as for pmtfifo_pop(3), except for an additional return value, PMTFIFO_EOF, which indicates an end of processing condition through an EOF token pushed by pmtmanagedfifo_push_eof calls. Note that all data objects that are popped out are in fact copies of the original objects that were pushed onto the queues. If the consumer wants to know the ordinal sequence number of the data object as it was written into the queue, then a non-NULL value of sequence can be used to return this number (this is generally useful in testing situations).
pmtmanagedfifo_display will display one or all currently defined managed fifos using elog_notify(3). A prefix string for each line of output can be specified by pre. If mtf is NULL, then all of the currently defined fifos are displayed.
These managed fifo routines provide a mechanism for a group of readers to all see the same data objects by keeping internal reference counts of data objects and by always providing copies of the data objects to the various readers. Once the reference count for a data object has gone to 0, the data object is automatically freed using the hook(3) facilities. This means that the virtual FIFO queue will not actually release data objects until all readers have read that object. It is the responsibility of the application programmer to insure this happens. Note that with this implementation all of the data objects returned by pmtmanagedfifo_pop become the responsibility of the calling routine; i.e. they need to be freed when they are no longer needed. This is easy to do using free_hook(3).
/* A test for the pmtmanagedfifo routines. */ #include <stdio.h> #include <stdlib.h> #include "brttutil.h" #define NUMBER_FIFOS 10 #define NUMBER_READER_THREADS 20 #define NUMBER_WRITER_THREADS 5 #define NUMBER_DATA_OBJECTS 100 Pmtmanagedfifo *fifos[NUMBER_FIFOS]; typedef struct data_ { char *buffer; int nchar; } Data; typedef struct readerthr_ { pthread_t thread; char * name; } ReaderThread; ReaderThread reader_threads[NUMBER_READER_THREADS]; void *reader_thread (void *arg); typedef struct writerthr_ { pthread_t thread; char * name; } WriterThread; WriterThread writer_threads[NUMBER_WRITER_THREADS]; void *writer_thread (void *arg); int fill_index=0; void data_free (void *datav); Hook *data_copy (Hook *datah); int main (int argc, char **argv) { int i, j; char name[64]; unsigned long size; /* create some managed fifos */ for (i=0; i<NUMBER_FIFOS; i++) { sprintf (name, "fifo%2.2d", i); fifos[i] = pmtmanagedfifo_create (name); if (fifos[i] == NULL) { die (0, "pmtmanagedfifo_create(%s) error.\n", name); } } /* set up consumers */ size = NUMBER_READER_THREADS*sizeof(ReaderThread); memset (reader_threads, 0, size); for (i=0; i<NUMBER_READER_THREADS; i++) { sprintf (name, "consumer%2.2d", i); reader_threads[i].name = strdup(name); for (j=0; j<NUMBER_FIFOS; j++) { if (pmtmanagedfifo_set_consumer (fifos[j], name, 0, 1) == NULL) { die (0, "pmtmanagedfifo_set_consumer(%s) error.\n", name); } } } /* display the fifos */ pmtmanagedfifo_display ("pmtmanagedfifo_test: ", NULL); /* launch a bunch of reader threads */ for (i=0; i<NUMBER_READER_THREADS; i++) { /* launch thread */ if (pthread_create (&(reader_threads[i].thread), NULL, reader_thread, &(reader_threads[i])) < 0) { die (1, "pthread_create(%s) error.\n", reader_threads[i].name); } } /* launch a bunch of writer threads */ for (i=0; i<NUMBER_WRITER_THREADS; i++) { sprintf (name, "writer%d", i); writer_threads[i].name = strdup(name); /* launch thread */ if (pthread_create (&(writer_threads[i].thread), NULL, writer_thread, &(writer_threads[i])) < 0) { die (1, "pthread_create(%s) error.\n", name); } } /* wait for everything to finish */ for (i=0; i<NUMBER_WRITER_THREADS; i++) { if (pthread_join (writer_threads[i].thread, NULL) != 0) { die (1, "pthread_join(%s) error.\n", writer_threads[i].name); } } elog_notify (0, "Writer threads done.\n"); for (i=0; i<NUMBER_READER_THREADS; i++) { if (pthread_join (reader_threads[i].thread, NULL) != 0) { die (1, "pthread_join(%s) error.\n", reader_threads[i].name); } } elog_notify (0, "Reader threads done - all OK.\n"); exit (0); } void * reader_thread (void *arg) { ReaderThread *rth = (ReaderThread *) arg; int i; int fifo_done[NUMBER_FIFOS]; unsigned long fifo_next_sequence[NUMBER_FIFOS]; int loop=1; memset (fifo_done, 0, NUMBER_FIFOS*sizeof(int)); memset (fifo_next_sequence, 0, NUMBER_FIFOS*sizeof(unsigned long)); /* loop over data packets */ while (loop) { /* loop over number of fifos */ loop = 0; for (i=0; i<NUMBER_FIFOS; i++) { Hook *datah; Data *data; unsigned long sequence; int ret; /* skip if this fifo is finished */ if (fifo_done[i]) continue; loop = 1; /* pop data object */ ret = pmtmanagedfifo_pop (fifos[i], rth->name, &datah, &sequence); if (ret < 0) { die (0, "reader_thread: %s: %s: pmtmanagedfifo_pop() error.\n", rth->name, fifos[i]->name); } if (ret != PMTFIFO_OK) { die (0, "reader_thread: %s: %s: pmtmanagedfifo_pop() wrong return value.\n", rth->name, fifos[i]->name); } data = (Data *) datah->p; /* check sequence number */ if (sequence != fifo_next_sequence[i]) { die (0, "reader_thread: %s: %s: sequence not in order (%d != %d).\n", rth->name, fifos[i]->name, sequence, fifo_next_sequence[i]); } (fifo_next_sequence[i])++; /* look for end packet */ if (data->nchar == strlen("writerdone")+1 && !strcmp(data->buffer, "writerdone")) { fifo_done[i] = 1; continue; } /* check data object contents */ if (check_random ((unsigned char *)data->buffer, data->nchar) != 0) { die (0, "reader_thread: %s: %s: wrong buffer contents.\n", rth->name, fifos[i]->name); } /* free packet */ free_hook (&datah); } } return (NULL); } void * writer_thread (void *arg) { WriterThread *wth = (WriterThread *) arg; int i, j; Data data; Hook data_hook; /* initialize data and data_hook structures making sure to set data_hook.free callback and set data_hook.p data pointer to our Data structure pointer. Note that since data and data_hook are declared on the stack as the structures themselves, as opposed to pointers to dynamically allocated structures, we only need to do this once. */ memset (&data, 0, sizeof(Data)); memset (&data_hook, 0, sizeof(Hook)); data_hook.free = data_free; data_hook.p = &data; /* loop over number of data objects per fifo */ for (i=0; i<NUMBER_DATA_OBJECTS; i++) { /* loop over number of fifos */ for (j=0; j<NUMBER_FIFOS; j++) { /* fill in a data object */ data.nchar = 0; while (data.nchar < 32) data.nchar = 0x3ff & rand(); if (data.buffer) free (data.buffer); data.buffer = malloc((unsigned long)data.nchar); if (data.buffer == NULL) { die (1, "writer_thread: malloc(data.buffer,%d) error.\n", data.nchar); } fill_random ((unsigned char *)data.buffer, data.nchar, &fill_index); /* push this onto the fifo queue. Not that our data structures need to be copied as they are put on the queue. */ if (pmtmanagedfifo_push (fifos[j], &data_hook, data_copy, 1) < 0) { die (0, "writer_thread: pmtmanagedfifo_push(%s) error.\n", fifos[j]->name); } } } /* push out a final set of data objects that mark the end */ for (j=0; j<NUMBER_FIFOS; j++) { if (data.buffer) free (data.buffer); data.buffer = strdup("writerdone"); if (data.buffer == NULL) { die (1, "writer_thread: strdup('writerdone') error.\n"); } data.nchar = strlen(data.buffer)+1; if (pmtmanagedfifo_push (fifos[j], &data_hook, data_copy, 1) < 0) { die (0, "writer_thread: pmtmanagedfifo_push(%s) error.\n", fifos[j]->name); } } return (NULL); } /* data free callback */ void data_free(void *datav) { Data *data = (Data *) datav; if (data == NULL) return; if (data->buffer) free (data->buffer); free (data); } /* data copy callback */ Hook * data_copy (Hook *datah) { Data *data = (Data *) datah->p; unsigned long size; Data *copy_data; Hook *copy; size = sizeof(Data); copy_data = malloc(size); if (copy_data == NULL) return (NULL); memset (copy_data, 0, size); *copy_data = *data; copy_data->buffer = malloc((unsigned long)data->nchar); if (copy_data->buffer == NULL) { free (copy_data); return (NULL); } memcpy (copy_data->buffer, data->buffer, (unsigned long)data->nchar); copy = new_hook (data_free); if (copy == NULL) { data_free (copy); return (NULL); } copy->p = copy_data; return (copy); } ruper% pmtmanagedfifo_test pmtmanagedfifo_test: Writer threads done. pmtmanagedfifo_test: Reader threads done - all OK. ruper%