miner_mover 0.0.0.4 → 0.1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +88 -16
- data/Rakefile +17 -2
- data/VERSION +1 -1
- data/demo/fiber.rb +11 -17
- data/demo/fiber_scheduler.rb +13 -20
- data/demo/{process.rb → process_pipe.rb} +37 -38
- data/demo/process_socket.rb +111 -0
- data/demo/ractor.rb +10 -28
- data/demo/serial.rb +8 -15
- data/demo/thread.rb +11 -21
- data/lib/miner_mover/config.rb +6 -3
- data/lib/miner_mover/run.rb +25 -8
- data/lib/miner_mover/timer.rb +67 -0
- data/lib/miner_mover/worker.rb +58 -37
- data/lib/miner_mover.rb +47 -3
- data/miner_mover.gemspec +7 -4
- metadata +15 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e2daa3ba8e778e20c25e3a6363683b3163e1141f9438b53be890845acf5440e
|
4
|
+
data.tar.gz: b72dc662be9c50caa281e087b3708c72a53aa5e593eeb053b59a0679238d568b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3203db4674a641c4a53ebed0cea4aaa445ef9e120ad582bc1cfcc787541ee53a8677eff1e4936429c4eaa72ef686ef97941a7a929d2d5db4e88a678684005962
|
7
|
+
data.tar.gz: e0052a82fa40bcf19b3382ead683087e52f90e6065751a65ff9475aadd141beb0ebe098673acffe48784f90c672aa5990fe2c0e117a3db4bd1782252af7cec91
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
[![Test Status](https://github.com/rickhull/miner_mover/actions/workflows/test.yaml/badge.svg)](https://github.com/rickhull/miner_mover/actions/workflows/test.yaml)
|
2
|
+
[![Demo Status](https://github.com/rickhull/miner_mover/actions/workflows/demo.yaml/badge.svg)](https://github.com/rickhull/miner_mover/actions/workflows/demo.yaml)
|
2
3
|
|
3
4
|
# Miner Mover
|
4
5
|
|
@@ -17,11 +18,12 @@ results as well. Ore is gathered at each depth; either a fixed amount or
|
|
17
18
|
randomized, based on depth. The amount of time spent mining each level is
|
18
19
|
independent and may be randomized.
|
19
20
|
|
20
|
-
https://github.com/rickhull/miner_mover/blob/
|
21
|
+
https://github.com/rickhull/miner_mover/blob/4f2a62f6d77316e780c7c13248698d4c57bb392e/lib/miner_mover/worker.rb#L96-L104
|
21
22
|
|
22
23
|
In this case, miners are rewarded by calculating `fibonacci(depth)`, using
|
23
|
-
classic, inefficient fibonacci.
|
24
|
-
|
24
|
+
classic, inefficient fibonacci.
|
25
|
+
`fibonacci(35)` yields around 10M ore, while `fibonacci(30)` yields under
|
26
|
+
1M ore.
|
25
27
|
|
26
28
|
## Moving
|
27
29
|
|
@@ -29,7 +31,7 @@ A mover has a batch size, say 10. As the mover accumulates ore over time,
|
|
29
31
|
once the batch size is reached, the mover delivers the ore to the destination.
|
30
32
|
Larger batches take longer. The delivery time can be randomized.
|
31
33
|
|
32
|
-
https://github.com/rickhull/miner_mover/blob/
|
34
|
+
https://github.com/rickhull/miner_mover/blob/4f2a62f6d77316e780c7c13248698d4c57bb392e/lib/miner_mover/worker.rb#L152-L183
|
33
35
|
|
34
36
|
The time and work spent delivering ore can be simulated three ways,
|
35
37
|
configured via `:work_type`
|
@@ -42,28 +44,76 @@ configured via `:work_type`
|
|
42
44
|
|
43
45
|
## Install
|
44
46
|
|
45
|
-
You'll want to use Ruby 3.x to make the most of Fibers.
|
47
|
+
You'll want to use **Ruby 3.x** (CRuby) to make the most of Fibers.
|
46
48
|
|
47
|
-
|
49
|
+
### Dependencies
|
48
50
|
|
49
51
|
* rake
|
50
52
|
* minitest
|
51
|
-
* compsci
|
52
53
|
* dotcfg
|
53
54
|
* fiber_scheduler
|
54
55
|
|
55
|
-
`gem install rake minitest
|
56
|
+
`gem install rake minitest dotcfg fiber_scheduler`
|
57
|
+
|
58
|
+
### Clone
|
59
|
+
|
60
|
+
```
|
61
|
+
git clone https://github.com/rickhull/miner_mover
|
62
|
+
cd miner_mover
|
63
|
+
```
|
64
|
+
|
65
|
+
### Try rake
|
66
|
+
|
67
|
+
Try: `rake -T` to see available [Rake tasks](Rakefile)
|
68
|
+
|
69
|
+
```
|
70
|
+
$ rake -T
|
71
|
+
|
72
|
+
rake alt_demo # run all demos minus fiber_scheduler / ractor / process
|
73
|
+
rake config # run demo/config
|
74
|
+
rake default # rake test
|
75
|
+
rake demo # run all demos
|
76
|
+
rake fiber # run demo/fiber
|
77
|
+
rake fiber_scheduler # run demo/fiber_scheduler
|
78
|
+
rake process_pipe # run demo/process_pipe
|
79
|
+
rake process_socket # run demo/process_socket
|
80
|
+
rake ractor # run demo/ractor
|
81
|
+
rake serial # run demo/serial
|
82
|
+
rake test # Run tests
|
83
|
+
rake thread # run demo/thread
|
84
|
+
```
|
85
|
+
|
86
|
+
Try: `rake test`
|
87
|
+
|
88
|
+
## Rake Tasks
|
89
|
+
|
90
|
+
Included demonstration scripts can be executed via Rake tasks.
|
91
|
+
The following order is recommended:
|
92
|
+
|
93
|
+
* `rake config`
|
94
|
+
* `rake serial`
|
95
|
+
* `rake fiber`
|
96
|
+
* `rake fiber_scheduler`
|
97
|
+
* `rake thread`
|
98
|
+
* `rake process_pipe`
|
99
|
+
* `rake process_socket`
|
100
|
+
|
101
|
+
Try each task; there will be about 6 seconds worth of many lines of output
|
102
|
+
logging. These rake tasks correspond to the scripts within [`demo/`](demo/).
|
56
103
|
|
57
104
|
## Satisfy `LOAD_PATH`
|
58
105
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
106
|
+
Rake tasks take care of `LOAD_PATH`, so the following is
|
107
|
+
**only necessary when *not* using rake tasks**:
|
108
|
+
|
109
|
+
* Execute scripts and irb sessions from the project root, e.g. `~/miner_mover`
|
110
|
+
* Use `-I lib` as a flag to `ruby` or `irb` to update `LOAD_PATH` so that
|
111
|
+
`require 'miner_mover'` will work.
|
112
|
+
* This project does not use `require_relative`
|
63
113
|
|
64
114
|
## Exploration in `irb`
|
65
115
|
|
66
|
-
`$ irb -
|
116
|
+
`$ irb -I lib`
|
67
117
|
|
68
118
|
```
|
69
119
|
irb(main):001:0> include MinerMover
|
@@ -105,13 +155,15 @@ multitasking paradigms in Ruby.
|
|
105
155
|
* [`demo/fiber_scheduler.rb`](demo/fiber_scheduler.rb)
|
106
156
|
* [`demo/thread.rb`](demo/thread.rb)
|
107
157
|
* [`demo/ractor.rb`](demo/ractor.rb)
|
158
|
+
* [`demo/process_pipe.rb`](demo/process_pipe.rb)
|
159
|
+
* [`demo/process_socket.rb`](demo/process_socket.rb)
|
108
160
|
|
109
161
|
See [config/example.cfg](config/example.cfg) for configuration.
|
110
162
|
It will be loaded by default.
|
111
|
-
Note that serial.rb and fiber.rb have no concurrency and cannot use
|
112
|
-
miners or movers.
|
163
|
+
Note that `serial.rb` and `fiber.rb` have no concurrency and cannot use
|
164
|
+
multiple miners or movers.
|
113
165
|
|
114
|
-
Execute via e.g. `ruby -Ilib demo/
|
166
|
+
Execute via e.g. `ruby -Ilib demo/thread.rb`
|
115
167
|
|
116
168
|
# Multitasking
|
117
169
|
|
@@ -319,3 +371,23 @@ Ractor.receive - returns a message from the current Ractor's incoming port
|
|
319
371
|
Ractor.yield - current Ractor sends a message on the outgoing port
|
320
372
|
Ractor#take - returns the next outgoing message from a Ractor
|
321
373
|
```
|
374
|
+
|
375
|
+
## Processes
|
376
|
+
|
377
|
+
There are many ways to create a process in Ruby, some more useful than
|
378
|
+
others. My favorites:
|
379
|
+
|
380
|
+
* `Process.fork` - when called with a block, the block is only executed in the
|
381
|
+
child subprocess
|
382
|
+
* `Process.spawn` - extensive options, nonblocking, call `Process.wait(pid)`
|
383
|
+
to get the result
|
384
|
+
* `Open3.popen3` - for access to `STDIN` `STDOUT` `STDERR`
|
385
|
+
|
386
|
+
### IPC
|
387
|
+
|
388
|
+
* Pipes
|
389
|
+
- `IO.pipe` (streaming / bytes / unidirectional)
|
390
|
+
* Unix sockets
|
391
|
+
- `UNIXSocket.pair :RAW`
|
392
|
+
- `UNIXSocket.pair :DGRAM` (datagram / message / "like UDP")
|
393
|
+
- `UNIXSocket.pair :STREAM` (streaming / bytes / "like TCP")
|
data/Rakefile
CHANGED
@@ -5,16 +5,31 @@ Rake::TestTask.new :test do |t|
|
|
5
5
|
t.warning = true
|
6
6
|
end
|
7
7
|
|
8
|
+
desc "rake test"
|
8
9
|
task default: :test
|
9
10
|
|
11
|
+
Dir['demo/*.rb'].each { |demo_path|
|
12
|
+
name = File.basename(demo_path, '.rb')
|
13
|
+
desc "run demo/#{name}"
|
14
|
+
task(name) { sh "ruby -Ilib #{demo_path}" }
|
15
|
+
}
|
16
|
+
|
17
|
+
# jruby / truffleruby lack fiber_scheduler, Ractor, and Process#fork
|
18
|
+
desc "run all demos minus fiber_scheduler / ractor / process"
|
19
|
+
task jvm_demo: [:serial, :fiber, :thread]
|
20
|
+
|
21
|
+
desc "run all demos"
|
22
|
+
task demo: [:serial, :fiber, :fiber_scheduler,
|
23
|
+
:thread, :ractor, :process_pipe, :process_socket]
|
24
|
+
|
10
25
|
begin
|
11
26
|
require 'buildar'
|
12
27
|
|
13
|
-
|
28
|
+
Buildar.new do |b|
|
14
29
|
b.gemspec_file = 'miner_mover.gemspec'
|
15
30
|
b.version_file = 'VERSION'
|
16
31
|
b.use_git = true
|
17
32
|
end
|
18
33
|
rescue LoadError
|
19
|
-
warn "buildar tasks unavailable"
|
34
|
+
# warn "buildar tasks unavailable"
|
20
35
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.1.1.1
|
data/demo/fiber.rb
CHANGED
@@ -2,13 +2,13 @@ require 'miner_mover/run'
|
|
2
2
|
|
3
3
|
include MinerMover
|
4
4
|
|
5
|
-
run = Run.new.cfg_banner!(duration: 1)
|
6
|
-
run.
|
7
|
-
run.log "Starting"
|
5
|
+
run = Run.new.cfg_banner!(duration: 1).start!
|
6
|
+
run.timestamp!
|
7
|
+
run.log "Starting #{__FILE__}"
|
8
8
|
|
9
9
|
stop_mining = false
|
10
10
|
Signal.trap("INT") {
|
11
|
-
run.
|
11
|
+
run.timestamp!
|
12
12
|
run.log " *** SIGINT *** Stop Mining"
|
13
13
|
stop_mining = true
|
14
14
|
}
|
@@ -17,20 +17,16 @@ Signal.trap("INT") {
|
|
17
17
|
miner = Fiber.new(blocking: true) {
|
18
18
|
run.log "MINE Mining operation started [ctrl-c] to stop"
|
19
19
|
m = run.new_miner
|
20
|
-
|
21
20
|
ore_mined = 0
|
22
21
|
|
23
|
-
|
24
|
-
while !stop_mining
|
22
|
+
while !stop_mining # SIGINT will trigger stop_mining = true
|
25
23
|
ore = m.mine_ore
|
26
|
-
|
27
|
-
# send any ore mined to the mover
|
28
|
-
Fiber.yield ore if ore > 0
|
29
24
|
ore_mined += ore
|
25
|
+
Fiber.yield ore if ore > 0
|
30
26
|
|
31
27
|
# stop mining after a while
|
32
28
|
if run.time_limit? or run.ore_limit?(ore_mined)
|
33
|
-
run.
|
29
|
+
run.timestamp!
|
34
30
|
m.log format("Mining limit reached: %s", Ore.display(ore_mined))
|
35
31
|
stop_mining = true
|
36
32
|
end
|
@@ -45,16 +41,14 @@ mover = run.new_mover
|
|
45
41
|
run.log "MOVE Moving operation started"
|
46
42
|
run.log "WAIT Waiting for ore ..."
|
47
43
|
|
44
|
+
# mover pulls from the queue, loads the ore, and moves it
|
48
45
|
loop {
|
49
|
-
# pick up ore yielded by the miner
|
50
46
|
ore = miner.resume
|
51
47
|
break if ore == :quit
|
52
|
-
|
53
|
-
# load (and possibly move) the ore
|
54
|
-
mover.load_ore ore if ore > 0
|
48
|
+
mover.load_ore ore if ore > 0 # move_batch happens when a batch is full
|
55
49
|
}
|
56
50
|
|
57
|
-
#
|
51
|
+
# move any remaining ore and quit
|
58
52
|
mover.move_batch while mover.batch > 0
|
59
53
|
run.log "QUIT #{mover.status}"
|
60
54
|
|
@@ -62,4 +56,4 @@ ore_mined = miner.resume
|
|
62
56
|
ore_moved = mover.ore_moved
|
63
57
|
run.log format("MINE %s mined (%i)", Ore.display(ore_mined), ore_mined)
|
64
58
|
run.log format("MOVE %s moved (%i)", Ore.display(ore_moved), ore_moved)
|
65
|
-
run.
|
59
|
+
run.timestamp!
|
data/demo/fiber_scheduler.rb
CHANGED
@@ -3,13 +3,13 @@ require 'fiber_scheduler'
|
|
3
3
|
|
4
4
|
include MinerMover
|
5
5
|
|
6
|
-
run = Run.new.cfg_banner!(duration: 1)
|
7
|
-
run.
|
8
|
-
run.log "Starting"
|
6
|
+
run = Run.new.cfg_banner!(duration: 1).start!
|
7
|
+
run.timestamp!
|
8
|
+
run.log "Starting #{__FILE__}"
|
9
9
|
|
10
10
|
stop_mining = false
|
11
11
|
Signal.trap("INT") {
|
12
|
-
run.
|
12
|
+
run.timestamp!
|
13
13
|
run.log " *** SIGINT *** Stop Mining"
|
14
14
|
stop_mining = true
|
15
15
|
}
|
@@ -35,20 +35,16 @@ FiberScheduler do
|
|
35
35
|
Fiber.schedule do
|
36
36
|
m = run.new_miner
|
37
37
|
m.log "MINE Miner #{i} started"
|
38
|
-
|
39
38
|
ore_mined = 0
|
40
39
|
|
41
|
-
|
42
|
-
while !stop_mining
|
40
|
+
while !stop_mining # SIGINT will trigger stop_mining = true
|
43
41
|
ore = m.mine_ore
|
44
|
-
|
45
|
-
# send any ore mined to the mover
|
46
|
-
queue.push(ore) if ore > 0
|
47
42
|
ore_mined += ore
|
43
|
+
queue.push(ore) if ore > 0
|
48
44
|
|
49
45
|
# stop mining after a while
|
50
46
|
if run.time_limit? or run.ore_limit?(ore_mined)
|
51
|
-
run.
|
47
|
+
run.timestamp!
|
52
48
|
m.log format("Mining limit reached: %s", Ore.display(ore_mined))
|
53
49
|
stop_mining = true
|
54
50
|
end
|
@@ -73,16 +69,14 @@ FiberScheduler do
|
|
73
69
|
m = run.new_mover
|
74
70
|
m.log "MOVE Mover #{i} started"
|
75
71
|
|
72
|
+
# movers pull from the queue, load the ore, and move it
|
76
73
|
loop {
|
77
|
-
# pick up ore from the miner until we get a :quit message
|
78
74
|
ore = queue.pop
|
79
75
|
break if ore == :quit
|
80
|
-
|
81
|
-
# load (and possibly move) the ore
|
82
|
-
m.load_ore ore if ore > 0
|
76
|
+
m.load_ore ore if ore > 0 # move_batch happens when a batch is full
|
83
77
|
}
|
84
78
|
|
85
|
-
#
|
79
|
+
# move any remaining ore and quit
|
86
80
|
m.move_batch while m.batch > 0
|
87
81
|
m.log "QUIT #{m.status}"
|
88
82
|
|
@@ -100,9 +94,8 @@ FiberScheduler do
|
|
100
94
|
# tell every mover to quit
|
101
95
|
run.num_movers.times { queue.push(:quit) }
|
102
96
|
|
103
|
-
# queue closes once it is empty
|
104
|
-
# should helpfully cause errors if something is out of sync
|
105
|
-
queue.close
|
97
|
+
# queue only closes once it is empty
|
98
|
+
queue.close # should helpfully cause errors if something is out of sync
|
106
99
|
end
|
107
100
|
end
|
108
101
|
|
@@ -114,4 +107,4 @@ total_moved += moved.pop until moved.empty?
|
|
114
107
|
|
115
108
|
run.log format("MINE %s mined (%i)", Ore.display(total_mined), total_mined)
|
116
109
|
run.log format("MOVE %s moved (%i)", Ore.display(total_moved), total_moved)
|
117
|
-
run.
|
110
|
+
run.timestamp!
|
@@ -3,22 +3,32 @@ require 'thread'
|
|
3
3
|
|
4
4
|
include MinerMover
|
5
5
|
|
6
|
-
run = Run.new.cfg_banner!(duration: 1)
|
7
|
-
run.
|
8
|
-
run.log "Starting"
|
6
|
+
run = Run.new.cfg_banner!(duration: 1).start!
|
7
|
+
run.timestamp!
|
8
|
+
run.log "Starting #{__FILE__}"
|
9
9
|
|
10
10
|
stop_mining = false
|
11
11
|
Signal.trap("INT") {
|
12
|
-
run.
|
12
|
+
run.timestamp!
|
13
13
|
run.log " *** SIGINT *** Stop Mining"
|
14
14
|
stop_mining = true
|
15
15
|
}
|
16
16
|
|
17
|
+
pipe_reader, pipe_writer = IO.pipe
|
18
|
+
|
19
|
+
def pipe_reader.pop
|
20
|
+
Ore.decode self.read(Ore::WORD_LENGTH)
|
21
|
+
end
|
22
|
+
|
23
|
+
def pipe_writer.push amt
|
24
|
+
self.write(Ore.encode(amt))
|
25
|
+
end
|
26
|
+
|
27
|
+
# the moving operation executes in its own Process
|
17
28
|
mover = Process.fork {
|
18
29
|
run.log "MOVE Moving operation started"
|
19
|
-
|
20
|
-
# mover
|
21
|
-
queue = Thread::Queue.new
|
30
|
+
pipe_writer.close # we're only using pipe_reader in this process
|
31
|
+
queue = Thread::Queue.new # distribute incoming ore to mover threads
|
22
32
|
|
23
33
|
# store the mover threads in an array
|
24
34
|
movers = Array.new(run.num_movers) { |i|
|
@@ -26,16 +36,11 @@ mover = Process.fork {
|
|
26
36
|
m = run.new_mover
|
27
37
|
m.log "MOVE Mover #{i} started"
|
28
38
|
|
39
|
+
# movers pull from the queue, load the ore, and move it
|
29
40
|
loop {
|
30
|
-
# a mover picks up ore from the queue
|
31
|
-
run.debug && m.log("POP ")
|
32
41
|
ore = queue.pop
|
33
|
-
run.debug && m.log("POPD #{ore}")
|
34
|
-
|
35
42
|
break if ore == :quit
|
36
|
-
|
37
|
-
# load (and possibly move) the ore
|
38
|
-
m.load_ore ore
|
43
|
+
m.load_ore ore # move_batch happens when a batch is full
|
39
44
|
}
|
40
45
|
|
41
46
|
# move any remaining ore and quit
|
@@ -45,22 +50,25 @@ mover = Process.fork {
|
|
45
50
|
}
|
46
51
|
}
|
47
52
|
|
53
|
+
# Miners feed this Process with ore
|
54
|
+
# Pass the ore into a queue for the movers
|
55
|
+
# When the miners say to quit, tell the movers to quit
|
48
56
|
run.log "WAIT Waiting for ore ..."
|
49
|
-
|
50
|
-
# TODO: pipe or unix socket for ore
|
51
|
-
|
52
|
-
TODO = :notyet
|
53
|
-
|
54
57
|
loop {
|
55
|
-
|
56
|
-
ore
|
57
|
-
break if ore == :quit
|
58
|
+
ore = pipe_reader.pop
|
59
|
+
break if ore == 0 # signal to quit
|
58
60
|
queue.push ore
|
59
61
|
}
|
62
|
+
|
63
|
+
# tell all the movers to quit and gather their results
|
64
|
+
run.num_movers.times { queue.push :quit }
|
65
|
+
ore_moved = movers.map { |thr| thr.value.ore_moved }.sum
|
66
|
+
run.log format("MOVE %s moved (%i)", Ore.display(ore_moved), ore_moved)
|
60
67
|
}
|
61
68
|
|
62
|
-
# our mining operation executes in the main
|
69
|
+
# our mining operation executes in the main process, here
|
63
70
|
run.log "MINE Mining operation started [ctrl-c] to stop"
|
71
|
+
pipe_reader.close # we're only using pipe_writer in this process
|
64
72
|
|
65
73
|
# store the miner threads in an array
|
66
74
|
miners = Array.new(run.num_miners) { |i|
|
@@ -69,22 +77,14 @@ miners = Array.new(run.num_miners) { |i|
|
|
69
77
|
m.log "MINE Miner #{i} started"
|
70
78
|
ore_mined = 0
|
71
79
|
|
72
|
-
|
73
|
-
while !stop_mining
|
80
|
+
while !stop_mining # SIGINT will trigger stop_mining = true
|
74
81
|
ore = m.mine_ore
|
75
|
-
|
76
|
-
# send any ore mined to the mover Ractor
|
77
|
-
if ore > 0
|
78
|
-
run.debug && m.log("SEND #{ore}")
|
79
|
-
mover.send ore
|
80
|
-
run.debug && m.log("SENT #{ore}")
|
81
|
-
end
|
82
|
-
|
83
82
|
ore_mined += ore
|
83
|
+
pipe_writer.push(ore) if ore > 0
|
84
84
|
|
85
85
|
# stop mining after a while
|
86
86
|
if run.time_limit? or run.ore_limit?(ore_mined)
|
87
|
-
run.
|
87
|
+
run.timestamp!
|
88
88
|
m.log format("Mining limit reached: %s", Ore.display(ore_mined))
|
89
89
|
stop_mining = true
|
90
90
|
end
|
@@ -101,9 +101,8 @@ ore_mined = miners.map { |thr| thr.value }.sum
|
|
101
101
|
run.log format("MINE %s mined (%i)", Ore.display(ore_mined), ore_mined)
|
102
102
|
|
103
103
|
# tell mover to quit
|
104
|
-
|
104
|
+
pipe_writer.push 0
|
105
105
|
|
106
106
|
# wait for results
|
107
|
-
|
108
|
-
run.
|
109
|
-
run.timer.timestamp!
|
107
|
+
Process.wait
|
108
|
+
run.timestamp!
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'miner_mover/run'
|
2
|
+
require 'thread'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
include MinerMover
|
6
|
+
|
7
|
+
run = Run.new.cfg_banner!(duration: 1).start!
|
8
|
+
run.timestamp!
|
9
|
+
run.log "Starting #{__FILE__}"
|
10
|
+
|
11
|
+
stop_mining = false
|
12
|
+
Signal.trap("INT") {
|
13
|
+
run.timestamp!
|
14
|
+
run.log " *** SIGINT *** Stop Mining"
|
15
|
+
stop_mining = true
|
16
|
+
}
|
17
|
+
|
18
|
+
socktype = [:DGRAM, :STREAM, :RAW].sample
|
19
|
+
run.log "SOCK #{socktype}"
|
20
|
+
csock, psock = UNIXSocket.pair(socktype)
|
21
|
+
|
22
|
+
def csock.pop
|
23
|
+
Ore.decode self.recv(Ore::WORD_LENGTH)
|
24
|
+
end
|
25
|
+
|
26
|
+
def psock.push amt
|
27
|
+
self.send(Ore.encode(amt), 0)
|
28
|
+
end
|
29
|
+
|
30
|
+
# the moving operation executes in its own Process
|
31
|
+
mover = Process.fork {
|
32
|
+
run.log "MOVE Moving operation started"
|
33
|
+
psock.close # we're only using csock in this process
|
34
|
+
queue = Thread::Queue.new # distribute incoming ore to mover threads
|
35
|
+
|
36
|
+
# store the mover threads in an array
|
37
|
+
movers = Array.new(run.num_movers) { |i|
|
38
|
+
Thread.new {
|
39
|
+
m = run.new_mover
|
40
|
+
m.log "MOVE Mover #{i} started"
|
41
|
+
|
42
|
+
# movers pull from the queue, load the ore, and move it
|
43
|
+
loop {
|
44
|
+
ore = queue.pop
|
45
|
+
break if ore == :quit
|
46
|
+
m.load_ore ore # move_batch happens when a batch is full
|
47
|
+
}
|
48
|
+
|
49
|
+
# move any remaining ore and quit
|
50
|
+
m.move_batch while m.batch > 0
|
51
|
+
m.log "QUIT #{m.status}"
|
52
|
+
m
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
# Miners feed this Process with ore
|
57
|
+
# Pass the ore into a queue for the movers
|
58
|
+
# When the miners say to quit, tell the movers to quit
|
59
|
+
run.log "WAIT Waiting for ore ..."
|
60
|
+
loop {
|
61
|
+
ore = csock.pop
|
62
|
+
break if ore == 0 # signal to quit
|
63
|
+
queue.push ore
|
64
|
+
}
|
65
|
+
|
66
|
+
# tell all the movers to quit and gather their results
|
67
|
+
run.num_movers.times { queue.push :quit }
|
68
|
+
ore_moved = movers.map { |thr| thr.value.ore_moved }.sum
|
69
|
+
run.log format("MOVE %s moved (%i)", Ore.display(ore_moved), ore_moved)
|
70
|
+
}
|
71
|
+
|
72
|
+
# our mining operation executes in the main process, here
|
73
|
+
run.log "MINE Mining operation started [ctrl-c] to stop"
|
74
|
+
csock.close # we're only using psock in this process
|
75
|
+
|
76
|
+
# store the miner threads in an array
|
77
|
+
miners = Array.new(run.num_miners) { |i|
|
78
|
+
Thread.new {
|
79
|
+
m = run.new_miner
|
80
|
+
m.log "MINE Miner #{i} started"
|
81
|
+
ore_mined = 0
|
82
|
+
|
83
|
+
while !stop_mining # SIGINT will trigger stop_mining = true
|
84
|
+
ore = m.mine_ore
|
85
|
+
ore_mined += ore
|
86
|
+
psock.push(ore) if ore > 0
|
87
|
+
|
88
|
+
# stop mining after a while
|
89
|
+
if run.time_limit? or run.ore_limit?(ore_mined)
|
90
|
+
run.timestamp!
|
91
|
+
m.log format("Mining limit reached: %s", Ore.display(ore_mined))
|
92
|
+
stop_mining = true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
m.log format("MINE Miner %i finished after mining %s",
|
97
|
+
i, Ore.display(ore_mined))
|
98
|
+
ore_mined
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
# wait on all mining threads to stop
|
103
|
+
ore_mined = miners.map { |thr| thr.value }.sum
|
104
|
+
run.log format("MINE %s mined (%i)", Ore.display(ore_mined), ore_mined)
|
105
|
+
|
106
|
+
# tell mover to quit
|
107
|
+
psock.push 0
|
108
|
+
|
109
|
+
# wait for results
|
110
|
+
Process.wait
|
111
|
+
run.timestamp!
|
data/demo/ractor.rb
CHANGED
@@ -3,13 +3,13 @@ require 'thread'
|
|
3
3
|
|
4
4
|
include MinerMover
|
5
5
|
|
6
|
-
run = Run.new.cfg_banner!(duration: 1)
|
7
|
-
run.
|
8
|
-
run.log "Starting"
|
6
|
+
run = Run.new.cfg_banner!(duration: 1).start!
|
7
|
+
run.timestamp!
|
8
|
+
run.log "Starting #{__FILE__}"
|
9
9
|
|
10
10
|
stop_mining = false
|
11
11
|
Signal.trap("INT") {
|
12
|
-
run.
|
12
|
+
run.timestamp!
|
13
13
|
run.log " *** SIGINT *** Stop Mining"
|
14
14
|
stop_mining = true
|
15
15
|
}
|
@@ -27,16 +27,11 @@ mover = Ractor.new(run) { |r|
|
|
27
27
|
m = r.new_mover
|
28
28
|
m.log "MOVE Mover #{i} started"
|
29
29
|
|
30
|
+
# movers pull from the queue, load the ore, and move it
|
30
31
|
loop {
|
31
|
-
# a mover picks up ore from the queue
|
32
|
-
r.debug && m.log("POP ")
|
33
32
|
ore = queue.pop
|
34
|
-
r.debug && m.log("POPD #{ore}")
|
35
|
-
|
36
33
|
break if ore == :quit
|
37
|
-
|
38
|
-
# load (and possibly move) the ore
|
39
|
-
m.load_ore ore
|
34
|
+
m.load_ore ore # move_batch happens when a batch is full
|
40
35
|
}
|
41
36
|
|
42
37
|
# move any remaining ore and quit
|
@@ -53,13 +48,8 @@ mover = Ractor.new(run) { |r|
|
|
53
48
|
loop {
|
54
49
|
# when the Ractor gets ore, push it into the queue
|
55
50
|
ore = Ractor.recv
|
56
|
-
r.debug && r.log("RECV #{ore}")
|
57
|
-
|
58
51
|
break if ore == :quit
|
59
|
-
|
60
|
-
r.debug && r.log("PUSH #{ore}")
|
61
52
|
queue.push ore
|
62
|
-
r.debug && r.log("PSHD #{ore}")
|
63
53
|
}
|
64
54
|
|
65
55
|
# tell all the movers to quit and gather their results
|
@@ -77,22 +67,14 @@ miners = Array.new(run.num_miners) { |i|
|
|
77
67
|
m.log "MINE Miner #{i} started"
|
78
68
|
ore_mined = 0
|
79
69
|
|
80
|
-
|
81
|
-
while !stop_mining
|
70
|
+
while !stop_mining # SIGINT will trigger stop_mining = true
|
82
71
|
ore = m.mine_ore
|
83
|
-
|
84
|
-
# send any ore mined to the mover Ractor
|
85
|
-
if ore > 0
|
86
|
-
run.debug && m.log("SEND #{ore}")
|
87
|
-
mover.send ore
|
88
|
-
run.debug && m.log("SENT #{ore}")
|
89
|
-
end
|
90
|
-
|
91
72
|
ore_mined += ore
|
73
|
+
mover.send ore if ore > 0 # send any ore mined to the mover Ractor
|
92
74
|
|
93
75
|
# stop mining after a while
|
94
76
|
if run.time_limit? or run.ore_limit?(ore_mined)
|
95
|
-
run.
|
77
|
+
run.timestamp!
|
96
78
|
m.log format("Mining limit reached: %s", Ore.display(ore_mined))
|
97
79
|
stop_mining = true
|
98
80
|
end
|
@@ -114,4 +96,4 @@ mover.send :quit
|
|
114
96
|
# wait for results
|
115
97
|
ore_moved = mover.take
|
116
98
|
run.log format("MOVE %s moved (%i)", Ore.display(ore_moved), ore_moved)
|
117
|
-
run.
|
99
|
+
run.timestamp!
|