miner_mover 0.0.0.4 → 0.1.1.1
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.
- 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
|
[](https://github.com/rickhull/miner_mover/actions/workflows/test.yaml)
|
2
|
+
[](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!
|