Class: Concurrent::MVar

Overview

An MVar is a synchronized single element container. They are empty or
contain one item. Taking a value from an empty MVar blocks, as does
putting a value into a full one. You can either think of them as blocking
queue of length one, or a special kind of mutable variable.

On top of the fundamental #put and #take operations, we also provide a
#mutate that is atomic with respect to operations on the same instance.
These operations all support timeouts.

We also support non-blocking operations #try_put! and #try_take!, a
#set! that ignores existing values, a #value that returns the value
without removing it or returns MVar::EMPTY, and a #modify! that yields
MVar::EMPTY if the MVar is empty and can be used to set MVar::EMPTY.
You shouldn't use these operations in the first instance.

MVar is related to M-structures in Id, MVar in Haskell and SyncVar in Scala.

Note that unlike the original Haskell paper, our #take is blocking. This is how
Haskell and Scala do it today.

Copy Options

Object references in Ruby are mutable. This can lead to serious
problems when the Concern::Dereferenceable#value of an object is a mutable reference. Which
is always the case unless the value is a Fixnum, Symbol, or similar
"primitive" data type. Each instance can be configured with a few
options that can help protect the program from potentially dangerous
operations. Each of these options can be optionally set when the object
instance is created:

:dup_on_deref When true the object will call the #dup method on
the value object every time the #value method is called
(default: false)

:freeze_on_deref When true the object will call the #freeze
method on the value object every time the #value method is called
(default: false)

:copy_on_deref When given a Proc object the Proc will be run
every time the #value method is called. The Proc will be given
the current value as its only argument and the result returned by
the block will be the return value of the #value call. When nil
this option will be ignored (default: nil)

When multiple deref options are set the order of operations is strictly defined.
The order of deref operations is:

:copy_on_deref

:dup_on_deref

:freeze_on_deref

Because of this ordering there is no need to #freeze an object created by a
provided :copy_on_deref block. Simply set :freeze_on_deref to true.
Setting both :dup_on_deref to true and :freeze_on_deref to true is
as close to the behavior of a "pure" functional language (like Erlang, Clojure,
or Haskell) as we are likely to get in Ruby.

#empty? ⇒ Boolean

#full? ⇒ Boolean

Returns if the MVar currently contains a value.

Returns:

(Boolean)

200
201
202

# File 'lib/concurrent/mvar.rb', line 200deffull?!empty?end

#modify(timeout = nil) ⇒ Object

Atomically take, yield the value to a block for transformation, and then
put the transformed value. Returns the transformed value. A timeout can
be set to limit the time spent blocked, in which case it returns TIMEOUT
if the time is exceeded.

Returns:

(Object)
—

the transformed value, or TIMEOUT

Raises:

(ArgumentError)

123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139

# File 'lib/concurrent/mvar.rb', line 123defmodify(timeout=nil)raiseArgumentError.new('no block given')unlessblock_given?@mutex.synchronizedowait_for_full(timeout)# If we timed out we'll still be empty
ifunlocked_full?value=@value@value=yieldvalue@full_condition.signalapply_deref_options(value)elseTIMEOUTendendend

#modify! ⇒ undocumented

Non-blocking version of modify that will yield with EMPTY if there is no value yet.

#put(value, timeout = nil) ⇒ Object

Put a value into an MVar, blocking if there is already a value until
it is empty. A timeout can be set to limit the time spent blocked, in
which case it returns TIMEOUT if the time is exceeded.

Returns:

(Object)
—

the value that was put, or TIMEOUT

103
104
105
106
107
108
109
110
111
112
113
114
115
116

# File 'lib/concurrent/mvar.rb', line 103defput(value,timeout=nil)@mutex.synchronizedowait_for_empty(timeout)# If we timed out we won't be empty
ifunlocked_empty?@value=value@full_condition.signalapply_deref_options(value)elseTIMEOUTendendend

#take(timeout = nil) ⇒ Object

Remove the value from an MVar, leaving it empty, and blocking if there
isn't a value. A timeout can be set to limit the time spent blocked, in
which case it returns TIMEOUT if the time is exceeded.

Returns:

(Object)
—

the value that was taken, or TIMEOUT

66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

# File 'lib/concurrent/mvar.rb', line 66deftake(timeout=nil)@mutex.synchronizedowait_for_full(timeout)# If we timed out we'll still be empty
ifunlocked_full?value=@value@value=EMPTY@empty_condition.signalapply_deref_options(value)elseTIMEOUTendendend

#try_put!(value) ⇒ undocumented

Non-blocking version of put, that returns whether or not it was successful.