/*
Copyright (C) 2001-2008, The Perl Foundation.
$Id: /mirror/trunk/src/pmc/tqueue.pmc 35692 2009-01-17T03:40:47.874808Z jkeenan  $

=head1 NAME

src/pmc/tqueue.pmc - Threadsafe Queue

=head1 DESCRIPTION

Threadsafe queue class for inter thread communication. If you have an
unthreaded program then please use an Array-like PMC.

     new P0, 'TQueue'
     push P0, some
     new P2, 'ParrotThread'
     ...

and in other thread (at least, when shared PMCs work :)

     shift P1, P0

Note: The TQueue must always be emptied before program exit.

=head2 Methods

=over 4

=cut

*/

#include "parrot/parrot.h"

pmclass TQueue need_ext is_shared {
    ATTR struct QUEUE *queue;
    ATTR INTVAL        thread_count;

/*

=item C<void init()>

Initializes the queue.

=cut

*/

    VTABLE void init() {
        Parrot_TQueue_attributes* attrs =
            mem_allocate_zeroed_typed(Parrot_TQueue_attributes);

        attrs->thread_count = 0;
        attrs->queue        = queue_init(0);
        PMC_data(SELF)      = attrs;

        PObj_custom_mark_destroy_SETALL(SELF);
    }

/*

=item C<PMC *clone()>

Returns the queue itself. No copy is made.

=cut

*/

    VTABLE PMC *clone() {
        /* XXX fake a shared PMC */
        return SELF;
    }

/*

=item C<void mark()>

Marks all the threads in the queue as live.

=cut

*/

    VTABLE void mark() {
        QUEUE       *queue;
        QUEUE_ENTRY *entry;

        GET_ATTR_queue(INTERP, SELF, queue);

        queue_lock(queue);
        entry = queue->head;

        while (entry) {
            pobject_lives(INTERP, (PObj *)entry->data);

            if (entry == queue->tail)
                break;

            entry = entry->next;
        }

        queue_unlock(queue);
    }

/*

=item C<void destroy()>

Destroys the queue.

=cut

*/

    VTABLE void destroy() {
        QUEUE *queue;
        GET_ATTR_queue(INTERP, SELF, queue);

        if (queue) {
#if 0
            /*
             * wait til queue is empty
             * XXX implement a time wait and PANIC if queue
             * isn't empty after some TIMEOUT
             */
            while (SELF.elements()) {
                queue_lock(queue);
                queue_wait(queue);
                queue_unlock(queue);
            }
#endif
            mem_sys_free(queue);
        }
        mem_sys_free(PMC_data(SELF));
    }

/*

=item C<INTVAL defined()>

Returns whether there are any threads in the queue.

=cut

*/

    VTABLE INTVAL defined() {
        return SELF.get_integer() != 0;
    }

/*

=item C<INTVAL get_integer()>

=cut

*/

    VTABLE INTVAL get_integer() {

        INTVAL thread_count;
        GET_ATTR_thread_count(INTERP, SELF, thread_count);
        return thread_count;
    }

/*

=item C<INTVAL elements()>

Returns the number of threads in the queue.

=cut

*/

    VTABLE INTVAL elements() {
        return SELF.get_integer();
    }

/*

=item C<void push_pmc(PMC *item)>

Adds the thread C<*item> to the end of the queue.

=cut

*/

    void push_pmc(PMC *item) {
        QUEUE_ENTRY * const entry = mem_allocate_typed(QUEUE_ENTRY);
        QUEUE       *       queue;
        INTVAL       thread_count;

        GET_ATTR_queue(INTERP, SELF, queue);

        /*
         * if item isn't shared nor const, then make
         *       a shared item
         */
        if (!(item->vtable->flags &
                    (VTABLE_IS_CONST_FLAG | VTABLE_IS_SHARED_FLAG)))
            VTABLE_share(INTERP, item);

        GC_WRITE_BARRIER(INTERP, SELF, NULL, item);

        entry->data = item;
        entry->type = QUEUE_ENTRY_TYPE_NONE;

        /* s. tsq.c:queue_push */
        queue_lock(queue);

        GET_ATTR_thread_count(INTERP, SELF, thread_count);
        ++thread_count;
        SET_ATTR_thread_count(INTERP, SELF, thread_count);

        /* Is there something in the queue? */
        if (queue->tail) {
            queue->tail->next = entry;
            queue->tail       = entry;
        }
        else {
            queue->head = entry;
            queue->tail = entry;
        }

        /* signal all waiters */
        queue_broadcast(queue);
        queue_unlock(queue);
    }

/*

=item C<PMC *shift_pmc()>

Removes the first thread from the start of the queue.

=cut

*/

    VTABLE PMC *shift_pmc() {
        QUEUE       *queue;
        QUEUE_ENTRY *entry;
        PMC         *ret;
        INTVAL       thread_count;

        GET_ATTR_queue(INTERP, SELF, queue);
        queue_lock(queue);

        while (queue->head == NULL) {
            queue_wait(queue);
        }

        entry = nosync_pop_entry(queue);
        GET_ATTR_thread_count(INTERP, SELF, thread_count);
        --thread_count;
        SET_ATTR_thread_count(INTERP, SELF, thread_count);

        queue_unlock(queue);

        ret   = (PMC *)entry->data;
        mem_sys_free(entry);

        return ret;
    }
}

/*

=back

=cut

*/

/*
 * Local variables:
 *   c-file-style: "parrot"
 * End:
 * vim: expandtab shiftwidth=4:
 */
