tupelo 0.14 → 0.15

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f4eb65df5eb526b3a885d5f0ce41736bb3ccc216
4
- data.tar.gz: 96f7e2b11e86b299af62b6598081564e98ceb159
3
+ metadata.gz: 79bc191b109fc7e11c3200a9e993725299558ead
4
+ data.tar.gz: 736b92e95728520e46c0516e857a26a2fd74e766
5
5
  SHA512:
6
- metadata.gz: bd91077b980c38858cb3b4549cec5df43ccb91a58bc209c026fe9db23c9ec6a90486c1cecd8f92c564d420fa26ef00cda93fa6b92e6e9af171d9abd23af78717
7
- data.tar.gz: b5de5df9f3e7baf6a02a4440b05e0cbb9a3fa04d37d5e46db3e0950ff5d59e29c23ac49809a9810f94fdd1a54916c47c98ce05a651167e72c9cfe8d60bd227ea
6
+ metadata.gz: 33db9c43e57b0405190165d965a09099b842dcc7bab9767ee7ec9f3ac11536de37fd8cc56f97709daa1c4c40c69a4f9055634ef318c90211148856c38a38adb9
7
+ data.tar.gz: a0b6f65625f735ec0b24bb06a02f07102661bf45b483a3b013ac7cafb4b3fceb27ac10711a7311638d2e305b2a522ea7eea1951739255ce0b31de10ee9bbe338
data/README.md CHANGED
@@ -1,5 +1,3 @@
1
- **NEWS**: Come hear a talk on Tupelo on December 11 in San Francisco at the [SF Distributed Computing meetup](http://www.meetup.com/San-Francisco-Distributed-Computing/events/153886592/). Abstract: [doc/sfdc.txt](doc/sfdc.md).
2
-
3
1
  tupelo
4
2
  ==
5
3
 
@@ -25,6 +23,7 @@ Documentation
25
23
 
26
24
  * [FAQ](doc/faq.md)
27
25
  * [Subspaces](doc/subspace.md)
26
+ * [Abstract](sfdc.md) and [slides](doc/sfdc.pdf) for San Francisco Distributed Systems meetup
28
27
 
29
28
  Getting started
30
29
  ==========
@@ -78,7 +77,12 @@ Getting started
78
77
 
79
78
  read_nowait <template>
80
79
 
81
- Read all tuples matching a template, no waiting:
80
+ Note that neither #read nor #read_nowait wait for any previously issued writes to complete. The difference is that #read waits for a match to exist and #read_nowait does not. Compare:
81
+
82
+ write [1]; read_nowait [1] # ==> nil, probably
83
+ write [2]; read [2] # ==> [2]
84
+
85
+ Read all tuples matching a template, no waiting (like #read_nowait):
82
86
 
83
87
  ra <template>
84
88
  read_all <template>
@@ -144,6 +148,17 @@ Getting started
144
148
  p t.read_nowait [3] # => nil
145
149
  end
146
150
 
151
+ Be careful about context within the do...end. If you omit the `|t|` block argument, then all operations are automatically scoped to the transaction, rather than the client. The following is equivalent to the previous example:
152
+
153
+ client = self # local var that we can use inside the block
154
+ transaction do
155
+ write [3]
156
+ p read [3]
157
+ p client.read_all
158
+ take [3]
159
+ p read_nowait [3]
160
+ end
161
+
147
162
  You can timeout a transaction:
148
163
 
149
164
  transaction timeout: 1 do
@@ -162,11 +177,11 @@ Getting started
162
177
 
163
178
  4. Run tup with a server file so that two sessions can interact. Do this in two terminals in the same dir:
164
179
 
165
- $ tup svr
180
+ $ tup sv
166
181
 
167
- (The 'svr' argument names a file that the first instance of tup uses to store information like socket addresses and the second instance uses to connect. The first instance starts the servers as child processes. However, both instances appear in the terminal as interactive shells.)
182
+ (The 'sv' argument names a file that the first instance of tup uses to store information like socket addresses and the second instance uses to connect. The first instance starts the servers as child processes. However, both instances appear in the terminal as interactive shells.)
168
183
 
169
- To do this on two hosts, copy the svr file and edit its hostname params as needed.
184
+ To do this on two hosts, copy the sv file and, if necessary, edit its connect_host field.
170
185
 
171
186
  5. Look at the examples. You may need to dig a bit to find the gem installation. For example:
172
187
 
@@ -176,17 +191,17 @@ Getting started
176
191
 
177
192
  6. Debugging: in addition to the --info switch on all bin and example programs, bin/tspy is also really useful; it shows all tuplespace events in sequence that they occur. For example, run
178
193
 
179
- $ tspy svr
194
+ $ tspy sv
180
195
 
181
- in another terminal after running `tup svr`. The output shows the clock tick, sending client, operation, and operation status (success or failure).
196
+ in another terminal after running `tup sv`. The output shows the clock tick, sending client, operation, and operation status (success or failure).
182
197
 
183
198
  There is also the similar --trace switch that is available to all bin and example programs. This turns on diagnostic output for each transaction. For example:
184
199
 
185
200
  ```
186
201
  tick cid status operation
187
- 1 2 batch write ["x", 1]
188
- 2 2 batch write ["y", 2]
189
- 3 3 atomic take ["x", 1], ["y", 2]
202
+ 1 2 write ["x", 1]
203
+ 2 2 write ["y", 2]
204
+ 3 3 take ["x", 1], ["y", 2]
190
205
  ```
191
206
 
192
207
  The `Tupelo.application` command, provided by `tupelo/app`, is the source of all these options and is available to your programs. It's a kind of lightweight process deployment and control framework; however `Tupelo.application` is not necessary to use tupelo.
@@ -303,7 +318,7 @@ Transactions combine operations into a group that take effect at the same instan
303
318
 
304
319
  However, it may take some time to prepare the transaction. This is true in terms of both real time (clock and process) and logical time (global sequence of operations). Preparing a transaction means finding tuples that match the criteria of the read and take operations. Finding tuples may require searching (locally) for tuples, or waiting for new tuples to be written by others. Also, the transaction may fail even after matching tuples are found (when another process takes tuples of interest). Then the transaction needs to be prepared again. Once prepared, transaction is sent to all clients, where it may either succeed (in all clients) or fail (for the same reason as before--someone else grabbed one of our tuples). If it fails, then the preparation begins again. A transaction guarantees that, when it completes, all the operations were performed on the tuples at the same logical time. It does not guarantee that the world stands still while one process is inside the `transaction {...}` block.
305
320
 
306
- Transactions are not just about batching up operations into a more efficient package (though you can do that with the #batch api). A transaction makes the combined operations execute atomically: the transaction finishes only when all of its operations can be successfully performed. Writes and pulses can always succeed, but takes and reads only succeed if the tuples exist.
321
+ Transactions are not just about batching up operations into a more efficient package. A transaction makes the combined operations execute atomically: the transaction finishes only when all of its operations can be successfully performed. Writes and pulses can always succeed, but takes and reads only succeed if the tuples exist.
307
322
 
308
323
  Transactions give you a means of optimistic locking: the transaction proceeds in a way that depends on preconditions. See [example/increment.rb](example/increment.rb) for a very simple example. Not only can you make a transaction depend on the existence of a tuple, you can make the effect of the transaction a function of existing tuples (see [example/transaction-logic.rb](example/transaction-logic.rb) and [example/broker-optimistic.rb](example/broker-optimistic.rb)).
309
324
 
data/bin/tspy CHANGED
@@ -3,18 +3,18 @@
3
3
  if ARGV.delete("-h") or ARGV.delete("--help")
4
4
  puts <<-END
5
5
  Usage:
6
- #$0 servers_file
6
+ #$0 services_file
7
7
 
8
- Connect to the tuplespace specified by the servers_file and
8
+ Connect to the tuplespace specified by the services_file and
9
9
  use the notification api to print out all events.
10
10
 
11
11
  For example, you can start a tup with
12
12
 
13
- tup svr
13
+ tup sv
14
14
 
15
15
  and then in another terminal
16
16
 
17
- tspy svr
17
+ tspy sv
18
18
 
19
19
  Options:
20
20
 
data/bin/tup CHANGED
@@ -5,21 +5,21 @@ if ARGV.delete("-h") or ARGV.delete("--help")
5
5
  Usage:
6
6
  #$0
7
7
 
8
- #$0 servers_file
9
- #$0 servers_file <script...
8
+ #$0 services_file
9
+ #$0 services_file <script...
10
10
 
11
- #$0 servers_file unix [path]
12
- #$0 servers_file tcp [host [port]]]
11
+ #$0 services_file unix [path]
12
+ #$0 services_file tcp [host [port]]]
13
13
 
14
- The first form starts a tuplespace server as a child process. Then it
14
+ The first form starts a tuplespace service as a child process. Then it
15
15
  enters an interactive session in which the current object is the proxy to
16
16
  that tuplespace. This form is useful for an isolated tuplespace for
17
17
  simple experiments.
18
18
 
19
- The second form tries to open the servers_file. If it cannot, then as in the
20
- first form, it starts a tuplespace server child process and writes its
21
- address to servers_file. If it can open the servers_file, then it simply
22
- connects to the referenced tuplespace server. In either case, as in the
19
+ The second form tries to open the services_file. If it cannot, then as in
20
+ the first form, it starts a tuplespace service child process and writes its
21
+ address to services_file. If it can open the services_file, then it simply
22
+ connects to the referenced tuplespace service. In either case, as in the
23
23
  first form, an interactive session starts. This form (in its two variants)
24
24
  is useful for starting two sessions operating on the same tuplespace.
25
25
 
@@ -29,13 +29,13 @@ if ARGV.delete("-h") or ARGV.delete("--help")
29
29
  transcripts.
30
30
 
31
31
  The fourth and fifth forms are like the previous, but can be used to expose
32
- the server on more or less public sockets with specified addresses. In the
33
- tcp case, the servers_file can be copied to other hosts and used with tup
34
- to connect to the servers (adjust the host references as needed). The
32
+ the service on more or less public sockets with specified addresses. In the
33
+ tcp case, the services_file can be copied to other hosts and used with tup
34
+ to connect to the services (adjust the host references as needed). The
35
35
  default for unix is, as in the first three forms, a path in a tmpdir. The
36
36
  default for tcp is localhost with port 0, and hence a dynamically chosen
37
- port. These forms are only for starting a new server; connecting to an
38
- existing server uses the simpler form "#$0 servers_file".
37
+ port. These forms are only for starting a new service; connecting to an
38
+ existing service uses the simpler form "#$0 services_file".
39
39
 
40
40
  Options:
41
41
 
@@ -62,9 +62,10 @@ if ARGV.delete("-h") or ARGV.delete("--help")
62
62
 
63
63
  --persist-dir DIR
64
64
  load and save tuplespace to DIR
65
+ (only needs to be set on first tup invocation)
65
66
 
66
67
  --use-subspaces
67
- enable subspaces for this tupelo server
68
+ enable subspaces for this tupelo service
68
69
  (only needs to be set on first tup invocation)
69
70
  --subscribe TAG,TAG,...
70
71
  subscribe to specified subspaces; use "" for none
@@ -86,13 +87,22 @@ if i=argv.index("--subscribe") # default is to subscribe to all
86
87
  subscribed_tags = argv.delete_at(i).split(",")
87
88
  end
88
89
 
89
- servers_file = argv.shift
90
- addr = argv.shift(3)
90
+ services_file = argv.shift
91
+ proto = (argv.shift || :unix).to_sym
92
+ addr = {proto: proto}
93
+ case proto
94
+ when :unix
95
+ addr[:path] = argv.shift
96
+ when :tcp
97
+ addr[:bind_host] = argv.shift
98
+ addr[:port] = argv.shift
99
+ addr[:port] = Integer(addr[:port]) if addr[:port]
100
+ end
91
101
 
92
102
  Tupelo.application(
93
103
  argv: argv,
94
104
  **tupelo_opts,
95
- servers_file: servers_file,
105
+ services_file: services_file,
96
106
  seqd_addr: addr,
97
107
  cseqd_addr: addr, # using same addr causes autoincrement of port/filename
98
108
  arcd_addr: addr) do
@@ -1,7 +1,7 @@
1
1
  # It's very easy to connect tup to an existing app.
2
2
  # Just run this file, and then do (in another terminal):
3
3
  #
4
- # ../bin/tup servers-nnnn.yaml
4
+ # ../bin/tup services-nnnn.yaml
5
5
  #
6
6
  # where nnnn is determined by looking in this dir. You can also
7
7
  # set the filename explicitly (first ARGV), rather than let it be generated
@@ -17,10 +17,10 @@
17
17
 
18
18
  require 'tupelo/app'
19
19
 
20
- filename = "servers-#$$.yaml"
20
+ filename = "services-#$$.yaml"
21
21
  puts "run this in another shell: tup #{filename}"
22
22
 
23
- Tupelo.application servers_file: filename do
23
+ Tupelo.application services_file: filename do
24
24
  child do
25
25
  loop do
26
26
  transaction do
@@ -0,0 +1,35 @@
1
+ # more like how you would do it in redis, except that the queue is not stored in
2
+ # the central server, so operations on it are not a bottleneck, FWIW
3
+
4
+ require 'tupelo/app'
5
+
6
+ N_PLAYERS = 10
7
+
8
+ Tupelo.application do
9
+ N_PLAYERS.times do
10
+ # sleep rand / 10 # reduce contention -- could also randomize inserts
11
+ child do
12
+ me = client_id
13
+ write name: me
14
+
15
+ you = transaction do
16
+ game = read_nowait(
17
+ player1: nil,
18
+ player2: me)
19
+ break game["player1"] if game
20
+
21
+ unless take_nowait name: me
22
+ raise Tupelo::Client::TransactionFailure
23
+ end
24
+
25
+ you = take(name: nil)["name"]
26
+ write(
27
+ player1: me,
28
+ player2: you)
29
+ you
30
+ end
31
+
32
+ log "now playing with #{you}"
33
+ end
34
+ end
35
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'tupelo/app'
4
4
 
5
- svr = "chat-nohistory.yaml"
5
+ sv = "chat-nohistory.yaml"
6
6
 
7
7
  Thread.abort_on_exception = true
8
8
 
@@ -13,7 +13,7 @@ def display_message msg
13
13
  puts "#{from}@#{time_str}> #{line}"
14
14
  end
15
15
 
16
- Tupelo.tcp_application servers_file: svr do
16
+ Tupelo.tcp_application services_file: sv do
17
17
  me = argv.shift
18
18
 
19
19
  local do
data/example/chat/chat.rb CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  require 'tupelo/app'
17
17
 
18
- svr = "chat.yaml"
18
+ sv = "chat.yaml"
19
19
  history_period = 60 # seconds -- discard _my_ messages older than this
20
20
 
21
21
  Thread.abort_on_exception = true
@@ -27,7 +27,7 @@ def display_message msg
27
27
  puts "#{from}@#{time_str}> #{line}"
28
28
  end
29
29
 
30
- Tupelo.tcp_application servers_file: svr do
30
+ Tupelo.tcp_application services_file: sv do
31
31
  me = argv.shift
32
32
 
33
33
  local do
@@ -0,0 +1,34 @@
1
+ require 'tupelo/app'
2
+
3
+ ### need a programmatic way to start up clients
4
+
5
+ Tupelo.application do |app|
6
+
7
+ app.child do ## local still hangs
8
+ 3.times do |i|
9
+ app.child do
10
+ write [i]
11
+ log "wrote #{i}"
12
+ end
13
+ end
14
+
15
+ 3.times do
16
+ log take [nil]
17
+ end
18
+ end
19
+ end
20
+
21
+ __END__
22
+
23
+ this hangs sometimes but not always:
24
+
25
+ tick cid status operation
26
+ A: client 3: wrote 0
27
+ A: client 4: wrote 1
28
+ 1 3 batch write [0]
29
+ 2 4 batch write [1]
30
+ A: client 2: [0]
31
+ 3 2 atomic take [0]
32
+ 4 2 atomic take [1]
33
+ A: client 2: [1]
34
+ A: client 5: wrote 2
data/example/fish01.rb ADDED
@@ -0,0 +1,48 @@
1
+ # This works, but requires a fix-up step.
2
+
3
+ require 'tupelo/app'
4
+
5
+ Tupelo.application do
6
+ 2.times do
7
+ child passive: true do
8
+ loop do
9
+ fish = nil
10
+
11
+ transaction do
12
+ fish, _ = take([String])
13
+ n, _ = take_nowait([Integer, fish])
14
+ if n
15
+ write [n + 1, fish]
16
+ else
17
+ write [1, fish] # another process might also write this, so ...
18
+ end
19
+ end
20
+ ### what if both processes die here?
21
+ transaction do # ... fix up the two tuples.
22
+ n1, _ = take_nowait [Integer, fish]; abort unless n1
23
+ n2, _ = take_nowait [Integer, fish]; abort unless n2
24
+ #log "fixing: #{[n1 + n2, fish]}"
25
+ write [n1 + n2, fish]
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ local do
32
+ seed = 3
33
+ srand seed
34
+ log "seed = #{seed}"
35
+
36
+ fishes = %w{ trout marlin char salmon }
37
+
38
+ a = fishes * 10
39
+ a.shuffle!
40
+ a.each do |fish|
41
+ write [fish]
42
+ end
43
+
44
+ fishes.each do |fish|
45
+ log take [10, fish]
46
+ end
47
+ end
48
+ end
@@ -2,12 +2,14 @@
2
2
 
3
3
  require 'tupelo/app/remote'
4
4
 
5
+ tunnel = !!ARGV.delete("--tunnel")
6
+
5
7
  hosts = ARGV.shift or abort "usage: #$0 <ssh-hostname>,<ssh-hostname>,..."
6
8
  hosts = hosts.split(",")
7
9
 
8
10
  Tupelo.tcp_application do
9
11
  hosts.each do |host|
10
- remote host: host, passive: true, eval: %{
12
+ remote host: host, passive: true, tunnel: tunnel, eval: %{
11
13
  loop do
12
14
  len = take([String])[0].size
13
15
  write [len]
@@ -0,0 +1,15 @@
1
+ #
2
+ # Minor optimization:
3
+
4
+ class KeyMatcher
5
+ def initialize i, n
6
+ @i = i
7
+ @n = n
8
+ end
9
+
10
+ def === id
11
+ id % @n == @i
12
+ end
13
+ end
14
+
15
+ vertex = take id: v_id_matcher, step: step, rank: nil, active: true
data/example/small.rb CHANGED
@@ -12,18 +12,18 @@ log_level = case
12
12
  else Logger::WARN
13
13
  end
14
14
 
15
- EasyServe.start(servers_file: "small-servers.yaml") do |ez|
15
+ EasyServe.start(services_file: "small-services.yaml") do |ez|
16
16
  log = ez.log
17
17
  log.level = log_level
18
18
  log.progname = "parent"
19
19
 
20
- ez.start_servers do
20
+ ez.start_services do
21
21
  arc_to_seq_sock, seq_to_arc_sock = UNIXSocket.pair
22
22
  arc_to_cseq_sock, cseq_to_arc_sock = UNIXSocket.pair
23
23
 
24
- ez.server :seqd do |svr|
24
+ ez.service :seqd do |sv|
25
25
  require 'funl/message-sequencer'
26
- seq = Funl::MessageSequencer.new svr, seq_to_arc_sock, log: log,
26
+ seq = Funl::MessageSequencer.new sv, seq_to_arc_sock, log: log,
27
27
  blob_type: 'msgpack' # the default
28
28
  #blob_type: 'marshal' # if you need to pass general ruby objects
29
29
  #blob_type: 'yaml' # less general ruby objects, but cross-language
@@ -31,15 +31,15 @@ EasyServe.start(servers_file: "small-servers.yaml") do |ez|
31
31
  seq.start
32
32
  end
33
33
 
34
- ez.server :cseqd do |svr|
34
+ ez.service :cseqd do |sv|
35
35
  require 'funl/client-sequencer'
36
- cseq = Funl::ClientSequencer.new svr, cseq_to_arc_sock, log: log
36
+ cseq = Funl::ClientSequencer.new sv, cseq_to_arc_sock, log: log
37
37
  cseq.start
38
38
  end
39
39
 
40
- ez.server :arcd do |svr|
40
+ ez.service :arcd do |sv|
41
41
  require 'tupelo/archiver'
42
- arc = Tupelo::Archiver.new svr,
42
+ arc = Tupelo::Archiver.new sv,
43
43
  seq: arc_to_seq_sock, cseq: arc_to_cseq_sock, log: log
44
44
  arc.start
45
45
  end
@@ -0,0 +1,106 @@
1
+ ## TODO
2
+ ##
3
+ ## scaling params
4
+
5
+ require 'tupelo/app'
6
+
7
+ ab_tag = "my address book"
8
+ ab_sort_field = 1
9
+ ab_val_field = 2
10
+ cmd_tag = "#{ab_tag} commands"
11
+ resp_tag = "#{ab_tag} responses"
12
+
13
+ Tupelo.application do
14
+ local do
15
+ use_subspaces!
16
+
17
+ # Subspace for tuples belonging to the addr book.
18
+ define_subspace(
19
+ tag: ab_tag,
20
+ template: [
21
+ {value: ab_tag},
22
+ {type: "string"}, # name <-- ab_sort_field references this field
23
+ nil # address; can be any object <-- ab_val_field
24
+ ]
25
+ )
26
+
27
+ # Subspace for commands for fetch and delete.
28
+ # We can't use #read and #take because then the requesting client
29
+ # would have to subscribe to the ab_tag subspace.
30
+ define_subspace(
31
+ tag: cmd_tag,
32
+ template: [
33
+ {value: cmd_tag},
34
+ {type: "string"}, # cmd name
35
+ {type: "list"} # arguments
36
+ ]
37
+ )
38
+
39
+ # Subspace for responses to commands. Identify the command this is in
40
+ # response to by copying it (alternately, could use ids).
41
+ define_subspace(
42
+ tag: resp_tag,
43
+ template: [
44
+ {value: resp_tag},
45
+ {type: "string"}, # cmd name
46
+ {type: "list"}, # arguments
47
+ nil # result of query -- type depends on command
48
+ ]
49
+ )
50
+ end
51
+
52
+ ## Could set N_SORTED_SET_SPACE > 1, but lookups are so fast it would
53
+ ## just lead to contention and redundant computation. Redundancy is useful
54
+ ## though.
55
+
56
+ # Inserts are just writes, which are handled by Worker and SortedSetSpace,
57
+ # so this child's app loop only needs to handle special commands: fetch and
58
+ # delete, which are delegated to the SortedSetSpace.
59
+ child tuplespace: [SortedSetSpace, ab_tag, ab_sort_field, ab_val_field],
60
+ subscribe: [ab_tag, cmd_tag], passive: true do
61
+ loop do
62
+ transaction do
63
+ _, cmd, args = take(subspace cmd_tag)
64
+
65
+ case cmd
66
+ when "delete"
67
+ args.each do |name|
68
+ take [ab_tag, name, nil]
69
+ end
70
+
71
+ when "fetch"
72
+ name = args[0]
73
+ _, _, addr = read [ab_tag, name, nil]
74
+ write [resp_tag, name, args, addr]
75
+
76
+ when "next", "prev"
77
+ name = args[0]
78
+ _, name2, addr = read SortedSetTemplate[ab_tag, cmd, name]
79
+ write [resp_tag, name, args, name2, addr]
80
+
81
+ when "first", "last"
82
+ _, name, addr = read SortedSetTemplate[ab_tag, cmd]
83
+ write [resp_tag, name, args, name, addr]
84
+
85
+ else # maybe write an error message in a tuple
86
+ log.error "bad command: #{cmd}"
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ child subscribe: resp_tag do
93
+ # write some ab entries
94
+ write [ab_tag, "McFirst, Firsty", "123 W. Crescent Terrace"]
95
+ write [ab_tag, "Secondismus, Deuce", "456 S. West Way"]
96
+
97
+ # make some queries
98
+ write [cmd_tag, "first", []]
99
+ *, name, addr = take [resp_tag, "first", [], nil, nil]
100
+ log "first entry: #{name} => #{addr}"
101
+
102
+ write [cmd_tag, "next", [name]]
103
+ *, name, addr = take [resp_tag, "next", [name], nil, nil]
104
+ log "next entry: #{name} => #{addr}"
105
+ end
106
+ end