strand 0.1.0 → 0.2.0.rc0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. data/.gitignore +3 -0
  2. data/CHANGELOG +13 -0
  3. data/Gemfile +3 -15
  4. data/LICENSE.txt +1 -0
  5. data/README.rdoc +12 -19
  6. data/Rakefile +10 -57
  7. data/lib/strand.rb +151 -126
  8. data/lib/strand/atc.rb +1 -1
  9. data/lib/strand/em/condition_variable.rb +57 -0
  10. data/lib/strand/em/mutex.rb +63 -0
  11. data/lib/strand/em/queue.rb +84 -0
  12. data/lib/strand/em/thread.rb +305 -0
  13. data/lib/strand/monitor.rb +193 -0
  14. data/lib/strand/version.rb +3 -0
  15. data/spec/spec_helper.rb +9 -5
  16. data/spec/strand/alive.rb +62 -0
  17. data/spec/strand/condition_variable.rb +10 -0
  18. data/spec/strand/condition_variable/broadcast.rb +61 -0
  19. data/spec/strand/condition_variable/signal.rb +62 -0
  20. data/spec/strand/condition_variable/wait.rb +20 -0
  21. data/spec/strand/current.rb +15 -0
  22. data/spec/strand/exit.rb +148 -0
  23. data/spec/strand/join.rb +60 -0
  24. data/spec/strand/local_storage.rb +98 -0
  25. data/spec/strand/mutex.rb +244 -0
  26. data/spec/strand/pass.rb +9 -0
  27. data/spec/strand/queue.rb +124 -0
  28. data/spec/strand/raise.rb +142 -0
  29. data/spec/strand/run.rb +5 -0
  30. data/spec/strand/shared.rb +14 -0
  31. data/spec/strand/sleep.rb +51 -0
  32. data/spec/strand/status.rb +44 -0
  33. data/spec/strand/stop.rb +58 -0
  34. data/spec/strand/strand.rb +32 -0
  35. data/spec/strand/value.rb +39 -0
  36. data/spec/strand/wakeup.rb +60 -0
  37. data/spec/strand_spec.rb +51 -0
  38. data/spec/support/fixtures.rb +305 -0
  39. data/spec/support/scratch.rb +17 -0
  40. data/spec/thread_spec.rb +20 -0
  41. data/strand.gemspec +23 -0
  42. metadata +72 -58
  43. data/Gemfile.lock +0 -40
  44. data/lib/strand/condition_variable.rb +0 -78
  45. data/spec/condition_variable_spec.rb +0 -82
  46. data/test/helper.rb +0 -30
  47. data/test/test_strand.rb +0 -121
@@ -0,0 +1,9 @@
1
+
2
+ shared_examples_for "Strand#pass" do
3
+
4
+ describe "pass" do
5
+ it "returns nil" do
6
+ Strand.pass.should == nil
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,124 @@
1
+ shared_examples_for "a queue" do
2
+
3
+ # These specs are derived from rubyspec for ruby's standard Queue class
4
+
5
+ context :enqueue do
6
+
7
+ it "adds an element to the Queue" do
8
+ q = Strand::Queue.new
9
+ q.size.should == 0
10
+ q << Object.new
11
+ q.size.should == 1
12
+ q.push(Object.new)
13
+ q.size.should == 2
14
+ q.enq(Object.new)
15
+ q.size.should == 3
16
+ end
17
+
18
+ end
19
+
20
+ context :dequeue do
21
+ it "removes an item from the Queue" do
22
+ q = Strand::Queue.new
23
+ q << Object.new
24
+ q.size.should == 1
25
+ q.pop
26
+ q.size.should == 0
27
+ end
28
+
29
+ it "returns items in the order they were added" do
30
+ q = Strand::Queue.new
31
+ q << 1
32
+ q << 2
33
+ q.deq.should == 1
34
+ q.shift.should == 2
35
+ end
36
+
37
+ it "blocks until there are items in the queue" do
38
+ q = Strand::Queue.new
39
+ v = 0
40
+
41
+ s = Strand.new do
42
+ q.pop
43
+ v = 1
44
+ end
45
+
46
+ v.should == 0
47
+ q << Object.new
48
+ s.join()
49
+ v.should == 1
50
+ end
51
+
52
+ it "raises a StrandError if Queue is empty" do
53
+ q = Strand::Queue.new
54
+ lambda { q.pop(true) }.should raise_error(strand_exception)
55
+ end
56
+ end
57
+
58
+ context :length do
59
+ it "returns the number of elements" do
60
+ q = Strand::Queue.new
61
+ q.length.should == 0
62
+ q << Object.new
63
+ q << Object.new
64
+ q.length.should == 2
65
+ end
66
+ end
67
+
68
+ context :empty do
69
+ it "returns true on an empty Queue" do
70
+ q = Strand::Queue.new
71
+ q.empty?.should be_true
72
+ end
73
+
74
+ it "returns false when Queue is not empty" do
75
+ q = Strand::Queue.new
76
+ q << Object.new
77
+ q.empty?.should be_false
78
+ end
79
+ end
80
+
81
+ context :num_waiting do
82
+ it "reports the number of Strands waiting on the Queue" do
83
+ q = Strand::Queue.new
84
+ fibers = []
85
+
86
+ 5.times do |i|
87
+ q.num_waiting.should == i
88
+ f = Strand.new { q.deq }
89
+ Strand.pass until f.status and f.status == 'sleep'
90
+ fibers << f
91
+ end
92
+
93
+ fibers.each { q.enq Object.new }
94
+
95
+ fibers.each { |f| Strand.pass while f.alive? }
96
+
97
+ q.num_waiting.should == 0
98
+ end
99
+ end
100
+
101
+ context :doc_example do
102
+ it "handles the doc example" do
103
+ queue = Strand::Queue.new
104
+
105
+ producer = Strand.new do
106
+ 5.times do |i|
107
+ Strand.sleep rand(i/4) # simulate expense
108
+ queue << i
109
+ puts "#{i} produced"
110
+ end
111
+ end
112
+
113
+ consumer = Strand.new do
114
+ 5.times do |i|
115
+ value = queue.pop
116
+ Strand.sleep rand(i/8) # simulate expense
117
+ puts "consumed #{value}"
118
+ end
119
+ end
120
+
121
+ consumer.join
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,142 @@
1
+ shared_examples_for "Strand#raise" do
2
+ context :raise do
3
+ it "ignores dead strands" do
4
+ t = Strand.new { :dead }
5
+ Strand.pass while t.alive?
6
+ lambda {t.raise("Kill the strand")}.should_not raise_error
7
+ lambda {t.value}.should_not raise_error
8
+ end
9
+ end
10
+
11
+ context "raise on a sleeping strand" do
12
+ before :each do
13
+ ScratchPad.clear
14
+ @str = StrandSpecs.sleeping_strand
15
+ Strand.pass while @str.status and @str.status != "sleep"
16
+ end
17
+
18
+ after :each do
19
+ @str.kill
20
+ end
21
+
22
+ it "raises a RuntimeError if no exception class is given" do
23
+ @str.raise
24
+ Strand.pass while @str.status
25
+ ScratchPad.recorded.should be_kind_of(RuntimeError)
26
+ end
27
+
28
+ it "raises the given exception" do
29
+ @str.raise Exception
30
+ Strand.pass while @str.status
31
+ ScratchPad.recorded.should be_kind_of(Exception)
32
+ end
33
+
34
+ it "raises the given exception with the given message" do
35
+ @str.raise Exception, "get to work"
36
+ Strand.pass while @str.status
37
+ ScratchPad.recorded.should be_kind_of(Exception)
38
+ ScratchPad.recorded.message.should == "get to work"
39
+ end
40
+
41
+ it "is captured and raised by Strand#value" do
42
+ t = Strand.new do
43
+ Strand.sleep
44
+ end
45
+
46
+ StrandSpecs.spin_until_sleeping(t)
47
+
48
+ t.raise
49
+ lambda { t.value }.should raise_error(RuntimeError)
50
+ end
51
+
52
+ it "raises a RuntimeError when called with no arguments" do
53
+ t = Strand.new do
54
+ begin
55
+ 1/0
56
+ rescue ZeroDivisionError
57
+ Strand.sleep 3
58
+ end
59
+ end
60
+ begin
61
+ raise RangeError
62
+ rescue
63
+ StrandSpecs.spin_until_sleeping(t)
64
+ t.raise
65
+ end
66
+ lambda {t.value}.should raise_error(RuntimeError)
67
+ t.kill
68
+ end
69
+ end
70
+
71
+ #TODO do these make sense?
72
+ quarantine! do
73
+
74
+ context "Strand#raise on a running strand" do
75
+ before :each do
76
+ ScratchPad.clear
77
+ StrandSpecs.clear_state
78
+
79
+ @str = StrandSpecs.running_strand
80
+ Strand.pass until StrandSpecs.state == :running
81
+ end
82
+
83
+ after :each do
84
+ @str.kill
85
+ end
86
+
87
+ it "raises a RuntimeError if no exception class is given" do
88
+ @str.raise
89
+ Strand.pass while @str.status
90
+ ScratchPad.recorded.should be_kind_of(RuntimeError)
91
+ end
92
+
93
+ it "raises the given exception" do
94
+ @str.raise Exception
95
+ Strand.pass while @str.status
96
+ ScratchPad.recorded.should be_kind_of(Exception)
97
+ end
98
+
99
+ it "raises the given exception with the given message" do
100
+ @str.raise Exception, "get to work"
101
+ Strand.pass while @str.status
102
+ ScratchPad.recorded.should be_kind_of(Exception)
103
+ ScratchPad.recorded.message.should == "get to work"
104
+ end
105
+
106
+ it "can go unhandled" do
107
+ t = Strand.new do
108
+ loop {}
109
+ end
110
+
111
+ t.raise
112
+ lambda {t.value}.should raise_error(RuntimeError)
113
+ end
114
+
115
+ it "raise the given argument even when there is an active exception" do
116
+ raised = false
117
+ t = Strand.new do
118
+ begin
119
+ 1/0
120
+ rescue ZeroDivisionError
121
+ raised = true
122
+ loop { }
123
+ end
124
+ end
125
+ begin
126
+ raise "Create an active exception for the current strand too"
127
+ rescue
128
+ Strand.pass until raised || !t.alive?
129
+ t.raise RangeError
130
+ lambda {t.value}.should raise_error(RangeError)
131
+ end
132
+ end
133
+
134
+ end
135
+ end
136
+ #TODO find out what the spec for :kernel_raise is
137
+ quarantine! do
138
+ context "Strand#raise on same strand" do
139
+ it_behaves_like :kernel_raise, :raise, Strand.current
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,5 @@
1
+
2
+ describe "Thread#run" do
3
+ it_behaves_like :thread_wakeup, :run
4
+ end
5
+
@@ -0,0 +1,14 @@
1
+
2
+ require 'strand/strand'
3
+ require 'strand/local_storage'
4
+ require 'strand/mutex'
5
+ require 'strand/queue'
6
+ require 'strand/condition_variable'
7
+
8
+ shared_examples_for Strand do
9
+ it_behaves_like "a strand"
10
+ it_behaves_like "strand local storage"
11
+ it_behaves_like "a mutex"
12
+ it_behaves_like "a queue"
13
+ it_behaves_like "a condition variable"
14
+ end
@@ -0,0 +1,51 @@
1
+
2
+ shared_examples_for "Strand#sleep" do
3
+ context "sleep" do
4
+ it "pauses execution for approximately the duration requested" do
5
+ duration = 0.1
6
+ start = Time.now
7
+ Strand.sleep duration
8
+ (Time.now - start).should be_within(0.1).of(duration)
9
+ end
10
+
11
+ it "returns the rounded number of seconds asleep" do
12
+ Strand.sleep(0.01).should be_kind_of(Integer)
13
+ end
14
+ it "raises a TypeError when passed a non-numeric duration" do
15
+ # Kernel.sleep raises error for nil, Strand.sleep will sleep forever
16
+ #lambda { Strand.sleep(nil) }.should raise_error(TypeError)
17
+ lambda { Strand.sleep('now') }.should raise_error(TypeError)
18
+ lambda { Strand.sleep('2') }.should raise_error(TypeError)
19
+ end
20
+
21
+ it "pauses execution indefinitely if not given a duration" do
22
+ lock = Strand::Queue.new
23
+ t = Strand.new do
24
+ lock << :ready
25
+ Strand.sleep
26
+ 5
27
+ end
28
+ lock.shift.should == :ready
29
+ # wait until the thread has gone to sleep
30
+ Strand.pass while t.status and t.status != "sleep"
31
+ t.run
32
+ t.value.should == 5
33
+ end
34
+
35
+ # Strand.sleep handles nil differently to kernel.sleep
36
+ it "pauses execution indefinitely if given a nil duration" do
37
+ lock = Strand::Queue.new
38
+ t = Strand.new do
39
+ lock << :ready
40
+ Strand.sleep(nil)
41
+ 5
42
+ end
43
+ lock.shift.should == :ready
44
+ # wait until the thread has gone to sleep
45
+ Strand.pass while t.status and t.status != "sleep"
46
+ t.run
47
+ t.value.should == 5
48
+ end
49
+ it "needs to be reviewed for spec completeness"
50
+ end
51
+ end
@@ -0,0 +1,44 @@
1
+ shared_examples_for "Strand#status" do
2
+ context :status do
3
+ it "can check it's own status" do
4
+ StrandSpecs.status_of_current_strand.status.should == 'run'
5
+ end
6
+
7
+ quarantine! do
8
+ # There's no way to interact with a running strand from another strand
9
+ it "describes a running strand" do
10
+ StrandSpecs.status_of_running_strand.status.should == 'run'
11
+ end
12
+ end
13
+
14
+ it "describes a sleeping strand" do
15
+ StrandSpecs.status_of_sleeping_strand.status.should == 'sleep'
16
+ end
17
+
18
+ it "describes a blocked strand" do
19
+ StrandSpecs.status_of_blocked_strand.status.should == 'sleep'
20
+ end
21
+
22
+ it "describes a completed strand" do
23
+ StrandSpecs.status_of_completed_strand.status.should == false
24
+ end
25
+
26
+ it "describes a killed strand" do
27
+ StrandSpecs.status_of_killed_strand.status.should == false
28
+ end
29
+
30
+ it "describes a strand with an uncaught exception" do
31
+ StrandSpecs.status_of_strand_with_uncaught_exception.status.should == nil
32
+ end
33
+
34
+ it "describes a dying sleeping strand" do
35
+ StrandSpecs.status_of_dying_sleeping_strand.status.should == 'sleep'
36
+ end
37
+
38
+ quarantine! do
39
+ it "reports aborting on a killed strand" do
40
+ StrandSpecs.status_of_aborting_strand.status.should == 'aborting'
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,58 @@
1
+
2
+ shared_examples_for "Strand#stop" do
3
+
4
+ context :stop do
5
+ it "causes the current strand to sleep indefinitely" do
6
+ t = Strand.new { Strand.stop; 5 }
7
+ Strand.pass while t.status and t.status != 'sleep'
8
+ t.status.should == 'sleep'
9
+ t.run
10
+ t.value.should == 5
11
+ end
12
+ end
13
+
14
+ context "stop?" do
15
+ it "can check it's own status" do
16
+ StrandSpecs.status_of_current_strand.stop?.should == false
17
+ end
18
+ quarantine! do
19
+ #Can't really have a running strand
20
+ it "describes a running strand" do
21
+ StrandSpecs.status_of_running_strand.stop?.should == false
22
+ end
23
+ end
24
+ it "describes a sleeping strand" do
25
+ StrandSpecs.status_of_sleeping_strand.stop?.should == true
26
+ end
27
+
28
+ it "describes a blocked strand" do
29
+ StrandSpecs.status_of_blocked_strand.stop?.should == true
30
+ end
31
+
32
+ it "describes a completed strand" do
33
+ StrandSpecs.status_of_completed_strand.stop?.should == true
34
+ end
35
+
36
+ it "describes a killed strand" do
37
+ StrandSpecs.status_of_killed_strand.stop?.should == true
38
+ end
39
+
40
+ it "describes a strand with an uncaught exception" do
41
+ StrandSpecs.status_of_strand_with_uncaught_exception.stop?.should == true
42
+ end
43
+
44
+ it "describes a dying running strand" do
45
+ StrandSpecs.status_of_dying_running_strand.stop?.should == false
46
+ end
47
+
48
+ it "describes a dying sleeping strand" do
49
+ StrandSpecs.status_of_dying_sleeping_strand.stop?.should == true
50
+ end
51
+
52
+ quarantine! do
53
+ it "reports aborting on a killed strand" do
54
+ StrandSpecs.status_of_aborting_strand.stop?.should == false
55
+ end
56
+ end
57
+ end
58
+ end