(* $Id$ *)

(** Condition variables *)

(** Condition variables are here defined as values that reside in shared heaps
    ({!Netmcore_heap}), for example in the header field of 
    {!Netmcore_array} or somewhere else in heap-allocated
    data structures.

    In order to ensure that the condition variable is in the heap, the
    special function [create_condition] must be used to initialize it
    there. As [create_condition] requires a mutator as argument, this is
    only possible by calling [create_condition] from the callback of
    {!Netmcore_heap.modify}.

    Condition variables are special values, and cannot be copied or moved.

    Condition variables are implemented on top of semaphores. Compared to
    the [pthreads] version of condition variables, the user needs here to
    allocate special [wait_entry] slots, one for each process. An entry
    can be used for all condition variables a process needs to wait for.
    (Actually, such entries also exist in typical [pthreads] implementations,
    but are hidden from the user in the thread control block. We just
    don't have here a place where we could allocate process-specific
    shared memory.)

    Since Ocamlnet-3.5, there are also special wait entries [wait_entry_e]
    which can be used to wait from within a running Unixqueue. For each
    such wait entry, however, a named pipe needs to be allocated.
 *)

type condition
  (** The condition variable *)

type wait_entry
  (** Each process that wants to [wait] needs a [wait_entry]. These entries
      can be used for several condition variables, so typically each process
      has only one entry for each heap.
   *)

type wait_entry_e
  (** A special kind of [wait_entry] for intergration into an event
      loop
   *)

type wait_set
  (** A set of [wait_entry], for easier management. This set can e.g. be
      stored side by side with the condition variable(s). It is important
      that the [wait_set] resides in the same shared heap as the
      condition variable.
   *)

val dummy_condition : unit -> condition
  (** A dummy condition is non-functional, but can be used to put something
      into [condition]-typed variables
   *)

val dummy_wait_set : unit -> wait_set
  (** A dummy [wait_set] is non-functional, but can be used to put something
      into [wait_set]-typed variables
   *)


val create_condition : Netmcore_heap.mutator -> condition
  (** [create m]: Creates a condition variable, and
      pushes it to the heap, using the mutator [m].

      After being pushed to the heap, the variable can be used. It is
      nonsense to copy it outside the heap.
   *)

val create_wait_set :  Netmcore_heap.mutator -> wait_set
  (** Creates a [wait_set] in the heap designated by the mutator *)

val alloc_wait_entry : Netmcore_heap.mutator -> wait_set -> wait_entry
  (** Allocates a [wait_entry] *)

val free_wait_entry : Netmcore_heap.mutator -> wait_set -> wait_entry -> unit
  (** Frees a [wait_entry] *)

val alloc_wait_entry_e : Netmcore_heap.mutator -> wait_set -> string ->
                            wait_entry_e
  (** [alloc_wait_entry_e mut set filename]: Allocates a new wait entry
      with notification via named pipe. The [filename] must refer to an
      existing named pipe.
   *)

val free_wait_entry_e : Netmcore_heap.mutator -> wait_set -> wait_entry_e -> 
                         unit
  (** Frees a [wait_entry_e]. The named pipe is deleted. *)

val wait : wait_entry -> condition -> Netmcore_mutex.mutex -> unit
  (** [wait we c m] atomically unlocks the mutex [m] and suspends the
      calling process on the condition variable [c]. The process will
      restart after the condition variable [c] has been signalled.
      The mutex [m] is locked again before [wait] returns.

      At the time of calling, the [wait_entry] [we] must not be used to
      manage another [wait].  When allocating a separate [wait_entry]
      per process this problem does not occur.
   *)

val wait_e : ?debug_name:string ->
             wait_entry_e -> condition -> Netmcore_mutex.mutex -> 
             Unixqueue.event_system ->
             (unit -> 'a Uq_engines.engine) ->
               'a Uq_engines.engine
  (** Like [wait], but the suspension is done by waiting on a
      named pipe event (i.e. "nonblocking suspension"):

      [wait_e we c m esys cont] atomically unlocks the mutex [m] and suspends
      the calling engine on the condition variable [c]. The engine will
      restart after the condition variable [c] has been signalled.
      The mutex [m] is locked again, at which time [cont] is called.
      The result of [cont()] is the result of [wait_e].

      At the time of calling, the [wait_entry_e] [we] must not be used to
      manage another [wait_e].  When allocating a separate [wait_entry_e]
      per process (or thread within the process) this problem does not occur.
   *)


(* UNCLEAR what this means:
      Another important restriction: There must be at most one 
      active [wait_e] per process or thread.
 *)


val signal : condition -> unit
  (** [signal c] restarts one of the processes waiting on the
      condition variable [c].
   *)

val broadcast : condition -> unit
  (** [broadcast c] restarts all processes waiting on the
      condition variable [c].
   *)

val pipe_name : wait_entry_e -> string
  (** Returns the name of the pipe *)

val destroy_condition : condition -> unit
val destroy_wait_set : wait_set -> unit
  (** Destroys these objects *)


module Debug : sig
  val enable : bool ref
end
