;;;; -*- mode:lisp;coding:utf-8 -*-
;;;;**************************************************************************
;;;;FILE:               queue.lisp
;;;;LANGUAGE:           Common-Lisp
;;;;SYSTEM:             Common-Lisp
;;;;USER-INTERFACE:     NONE
;;;;DESCRIPTION
;;;;
;;;;    A atomic non-negative queue, blocking on decrement at 0.
;;;;
;;;;AUTHORS
;;;;    <PJB> Pascal J. Bourguignon <pjb@informatimago.com>
;;;;MODIFICATIONS
;;;;    2017-04-16 <PJB> Added queue-empty-p.
;;;;    2015-08-29 <PJB> Created.
;;;;BUGS
;;;;LEGAL
;;;;    AGPL3
;;;;
;;;;    Copyright Pascal J. Bourguignon 2015 - 2017
;;;;
;;;;    This program is free software: you can redistribute it and/or modify
;;;;    it under the terms of the GNU Affero General Public License as published by
;;;;    the Free Software Foundation, either version 3 of the License, or
;;;;    (at your option) any later version.
;;;;
;;;;    This program is distributed in the hope that it will be useful,
;;;;    but WITHOUT ANY WARRANTY; without even the implied warranty of
;;;;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;;;    GNU Affero General Public License for more details.
;;;;
;;;;    You should have received a copy of the GNU Affero General Public License
;;;;    along with this program.  If not, see <http://www.gnu.org/licenses/>.
;;;;**************************************************************************
(eval-when (:compile-toplevel :load-toplevel :execute)
  (setf *readtable* (copy-readtable nil)))
(defpackage "COM.INFORMATIMAGO.CLEXT.QUEUE"
  (:use "COMMON-LISP"
        "BORDEAUX-THREADS")
  (:export "QUEUE" "QUEUEP"
           "MAKE-QUEUE"
           "QUEUE-NAME"
           "QUEUE-COUNT"
           "QUEUE-EMPTYP"
           "ENQUEUE"
           "DEQUEUE")
  (:documentation "Implements a thread-safe message queue."))
(in-package "COM.INFORMATIMAGO.CLEXT.QUEUE")

(defstruct (queue
            (:constructor make-queue
                (name
                 &aux
                   (lock (make-lock (format nil "~A-LOCK" name)))
                   (not-empty (make-condition-variable :name (format nil "~A-NOT-EMPTY" name)))))
            (:copier nil)
            (:predicate queuep))
  name head tail lock not-empty)

(eval-when (:compile-toplevel :load-toplevel :execute)
  (setf (documentation 'make-queue 'function) "
RETURN:     A new queue named NAME
"
        (documentation 'queue-name 'function) "
RETURN:     The name of the QUEUE.
"
        (documentation 'queue-head 'function) "
RETURN:     the head CONS cell of the QUEUE.
"
        (documentation 'queue-head 'function) "
RETURN:     the tail CONS cell of the QUEUE.
"
        (documentation 'queuep 'function) "
RETURN:     Predicate for the QUEUE type.
"
        (documentation 'queue-lock 'function) "
RETURN:     The lock of the QUEUE.
"
        (documentation 'queue-not-empty 'function) "
RETURN:     The NOT-EMPTY condition variable of the QUEUE.
"))

(defun enqueue (queue message)
  "
DO:         Atomically enqueues the MESSAGE in the QUEUE.  If the
            queue was empty, then a condition-notify is sent on the
            queue not-empty condition.

RETURN:     MESSAGE
"
  (with-lock-held ((queue-lock queue))
    (if (queue-tail queue)
        (setf (cdr (queue-tail queue)) (list message)
              (queue-tail queue) (cdr (queue-tail queue)))
        (progn
          (setf (queue-head queue) (setf (queue-tail queue) (list message)))
          (condition-notify (queue-not-empty queue)))))
  message)

(defun dequeue (queue)
  "
DO:         Atomically, dequeue the first message from the QUEUE.  If
            the queue is empty,  then wait on the not-empty condition
            of the queue.

RETURN:     the dequeued MESSAGE.
"
  (with-lock-held ((queue-lock queue))
    (loop :until (queue-head queue)
          :do (condition-wait (queue-not-empty queue) (queue-lock queue)))
    (if (eq (queue-head queue) (queue-tail queue))
        (prog1 (car (queue-head queue))
          (setf (queue-head queue) nil
                (queue-tail queue) nil))
        (pop (queue-head queue)))))

(defun queue-count (queue)
  "
RETURN:     The number of entries in the QUEUE.

NOTE:       The result may be falsified immediately, if another thread
            enqueues or dequeues.
"
  (with-lock-held ((queue-lock queue))
    (length (queue-head queue))))

(defun queue-emptyp (queue)
  "
RETURN:     Whether the queue is empty.

NOTE:       The result may be falsified immediately, becoming false if
            another thread enqueues, or becoming true if another
            thread dequeues.
"
  (not (queue-head queue)))

;;;; THE END ;;;;
ViewGit