tupelo 0.10 → 0.11

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: 892628e201d400fa9592a01b1fd62c4bb8e09ab5
4
- data.tar.gz: 2ca45f4f124ad6db32e2880557004f5c0553cf9f
3
+ metadata.gz: be780826d017e0ea442fa49d2a6403cc44cfe263
4
+ data.tar.gz: 133fa6c3218dd1fc4b06a8ea407a35ca3bdea0ac
5
5
  SHA512:
6
- metadata.gz: 0b8bfcdbb7143f543372cd0bdad95feb49305a47e57e0368146e3c224f33e1fddb261f9f019ec4c4a503a4250d91ecc653a93e5792c27e4f47cfd7ca33727a76
7
- data.tar.gz: 931b57069c7e06452429eb71e083c568be953a1e37de54cd1b9541107c6a79884e7d64645ed8765777c9ede5f18589e3c35d5057e946e82b8daa70c18180b49d
6
+ metadata.gz: 3327df2fc6fd2587dd244b99a4ed64827aeafd3314f7d3f34360e69f5598f294cd1c31010f8729c7ba913f1d1f489ce624735090074976de5aca19f88db5a646
7
+ data.tar.gz: 9b8c852daa50910f9c0823601a85618000404259893de1bc187393b8f0faeb99321ed06cbf78fb7ad488e9725326efa43202f201ae81c5a6d1e832cb9660cd3b
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  tupelo
2
2
  ==
3
3
 
4
- A tuplespace that is fast, scalable, and language agnostic.
4
+ A tuplespace that is fast, scalable, and language agnostic. It is designed for distribution of both computation and storage, in a unified language that has both transactional and tuple-operation (read/write/take) semantics.
5
+
5
6
 
6
7
  This is the reference implementation in ruby. It should be able to communicate with implementations in other languages. Planned implementation languages include C, Python, and Go.
7
8
 
@@ -191,7 +192,7 @@ A tuplespace is a service for coordination, configuration, and control of concur
191
192
 
192
193
  See https://en.wikipedia.org/wiki/Tuple_space for general information and history. This project is strongly influenced by Masatoshi Seki's Rinda implementation, part of the Ruby standard library. See http://pragprog.com/book/sidruby/the-druby-book for a good introduction to rinda and druby.
193
194
 
194
- See http://dbmsmusings.blogspot.com/2010/08/problems-with-acid-and-how-to-fix-them.html for an explanation of the imporance of determinism in distributed transaction systems.
195
+ See http://dbmsmusings.blogspot.com/2010/08/problems-with-acid-and-how-to-fix-them.html for an explanation of the importance of determinism in distributed transaction systems.
195
196
 
196
197
  What is a tuple?
197
198
  ----------------
@@ -318,11 +319,11 @@ Another use of transactions: forcing a retry when something changes:
318
319
 
319
320
  This code waits on the existence of a value, but retries if the step changes while waiting. See example/pregel/distributed.rb for a use of this techinique.
320
321
 
321
- Tupelo transactions are ACID in the following sense. They are Atomic and Isolated -- this is enforced by the transaction processing in each client. Consistency is enforced by the underlying message sequencer: each client's copy of the space is the deterministic result of the same sequence of operations. Durability is optional, but can be provided by the persistent archiver or other clients.
322
+ Tupelo transactions are ACID in the following sense. They are Atomic and Isolated -- this is enforced by the transaction processing in each client. Consistency is enforced by the underlying message sequencer: each client's copy of the space is the deterministic result of the same sequence of operations. This is also known as [sequential consistency] (https://en.wikipedia.org/wiki/Sequential_consistency). Durability is optional, but can be provided by the persistent archiver or other clients.
322
323
 
323
- On the CAP spectrum, tupelo tends towards consistency: for all clients, write and take operations are applied in the same order, so the state of the entire system up through a given tick of discrete time is universally agreed upon. Of course, because of the difficulties of distributed systems, one client may not yet have seen the same range of ticks as another.
324
+ On the CAP spectrum, tupelo tends towards consistency: for all clients, write and take operations are applied in the same order, so the state of the entire system up through a given tick of discrete time is universally agreed upon. This is known as [state machine replication] (http://en.wikipedia.org/wiki/State%20machine%20replication). Of course, because of the difficulties of distributed systems, one client may not yet have seen the same range of ticks as another. Tupelo's replication model (especially in the use of subspaces) can also be described as [virtual synchrony](https://en.wikipedia.org/wiki/Virtual_synchrony).
324
325
 
325
- Tupelo transactions do not require two-phase commit, because they are less powerful than general transactions. Each client has enough information to decide (in the same way as all other clients) whether the transaction succeeds or fails. This has performance advantages, but imposes some limitations on transactions over subspaces that are known to one client but not another.
326
+ Tupelo transactions do not require two-phase commit, because they are less powerful than general transactions. Each client has enough information to decide (in the same way as all other clients) whether the transaction succeeds or fails. This has performance advantages, but imposes some limitations on transactions over subspaces that are known to one client but not another. [Subspaces](doc/subspace.md).
326
327
 
327
328
 
328
329
  Syntax
@@ -532,11 +533,14 @@ Other gems:
532
533
 
533
534
  * yajl-ruby (only used to support --json option)
534
535
 
536
+ Optional gems for some of the examples:
537
+
538
+ * sinatra, http, sequel, sqlite, rbtree, leveldb-native
535
539
 
536
540
  Contact
537
541
  =======
538
542
 
539
- Joel VanderWerf, vjoel@users.sourceforge.net.
543
+ Joel VanderWerf, vjoel@users.sourceforge.net, @JoelVanderWerf.
540
544
 
541
545
  License and Copyright
542
546
  ========
data/bin/tup CHANGED
@@ -63,6 +63,12 @@ if ARGV.delete("-h") or ARGV.delete("--help")
63
63
  --persist-dir DIR
64
64
  load and save tuplespace to DIR
65
65
 
66
+ --use-subspaces
67
+ enable subspaces for this tupelo server
68
+ (only needs to be set on first tup invocation)
69
+ --subscribe TAG,TAG,...
70
+ subscribe to specified subspaces; use "" for none
71
+
66
72
  END
67
73
  exit
68
74
  end
@@ -73,6 +79,13 @@ argv, tupelo_opts = Tupelo.parse_args(ARGV)
73
79
 
74
80
  pubsub = argv.delete("--pubsub") # not a standard tupelo opt
75
81
 
82
+ use_subspaces = argv.delete("--use-subspaces") # not a standard tupelo opt
83
+
84
+ if i=argv.index("--subscribe") # default is to subscribe to all
85
+ argv.delete("--subscribe")
86
+ subscribed_tags = argv.delete_at(i).split(",")
87
+ end
88
+
76
89
  servers_file = argv.shift
77
90
  addr = argv.shift(3)
78
91
 
@@ -100,8 +113,14 @@ Tupelo.application(
100
113
  client_opts[:arc] = nil
101
114
  client_opts[:tuplespace] = TupClient::NullTuplespace
102
115
  end
116
+
117
+ if subscribed_tags
118
+ client_opts[:subscribe] = subscribed_tags
119
+ end
103
120
 
104
121
  local TupClient, **client_opts do
122
+ use_subspaces! if use_subspaces
123
+
105
124
  log.info {"cpu time: %.2fs" % Process.times.inject {|s,x|s+x}}
106
125
  log.info {"starting shell. Commands: #{TupClient::CMD_ALIASES.join(", ")}"}
107
126
 
@@ -1,4 +1,4 @@
1
- # see also parallel.rb
1
+ # see also ../parallel.rb and ../remote.rb
2
2
 
3
3
  require 'tupelo/app/remote'
4
4
 
@@ -19,6 +19,7 @@ Tupelo.application do
19
19
 
20
20
  N_PUBS.times do |pi|
21
21
  child do
22
+ log.progname = "pub #{pi}"
22
23
  read ['start']
23
24
  delay = pi
24
25
  sleep delay
@@ -0,0 +1,49 @@
1
+ # A tupelo cluster may expose its services using some other protocol so that
2
+ # process need not be tupelo-aware to use them. This example uses drb to expose
3
+ # services, but http or plain sockets would work too.
4
+
5
+ require 'drb'
6
+
7
+ rd, wr = IO.pipe # just for sharing the drb uri (or we could hardcode it)
8
+
9
+ fork do
10
+ rd.close
11
+ require 'tupelo/app'
12
+
13
+ Tupelo.application do
14
+ child do
15
+ DRb.start_service("druby://localhost:0", self)
16
+ wr.puts DRb.uri; wr.close
17
+ read ["done"]
18
+ end
19
+
20
+ child passive: true do
21
+ loop do
22
+ transaction do
23
+ _, x, y = take ["request", nil, nil]
24
+ write ["response", x, y, x + y]
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ fork do # No tupelo in this process.
32
+ wr.close
33
+ uri = rd.gets.chomp; rd.close
34
+ DRb.start_service(nil, nil)
35
+ tup_client = DRbObject.new(nil, uri)
36
+
37
+ tup_client.write ["request", 3, 4]
38
+ p tup_client.take ["response", 3, 4, nil]
39
+
40
+ tup_client.write ["request", "foo", "bar"]
41
+ p tup_client.take ["response", "foo", "bar", nil]
42
+
43
+ tup_client.write ["request", ["a", "b"], ["c", "d"]]
44
+ p tup_client.take ["response", ["a", "b"], ["c", "d"], nil]
45
+
46
+ tup_client.write ["done"]
47
+ end
48
+
49
+ Process.waitall
@@ -0,0 +1,67 @@
1
+ # A tupelo cluster may expose its services using some other protocol so that
2
+ # process need not be tupelo-aware to use them. This example uses http to expose
3
+ # services.
4
+ #
5
+ # Depends on the sinatra and http gems.
6
+
7
+ fork do
8
+ require 'tupelo/app'
9
+
10
+ Tupelo.application do
11
+ child do |client|
12
+ require 'sinatra/base'
13
+
14
+ Class.new(Sinatra::Base).class_eval do
15
+ get '/' do
16
+ "hello, world\n"
17
+ end
18
+
19
+ get '/add' do
20
+ x = Float(params["x"])
21
+ y = Float(params["y"])
22
+ client.write ["request", x, y]
23
+ resp = client.take ["response", x, y, nil]
24
+ resp.inspect + "\n"
25
+ end
26
+
27
+ get '/exit' do
28
+ Thread.new {sleep 1; exit}
29
+ "bye\n"
30
+ end
31
+
32
+ run!
33
+ end
34
+ end
35
+
36
+ child passive: true do
37
+ loop do
38
+ transaction do
39
+ _, x, y = take ["request", nil, nil]
40
+ write ["response", x, y, x + y]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ fork do # No tupelo in this process.
48
+ require 'http'
49
+
50
+ url = 'http://localhost:4567'
51
+
52
+ print "trying server at #{url}"
53
+ begin
54
+ print "."
55
+ HTTP.get url
56
+ rescue Errno::ECONNREFUSED
57
+ sleep 0.2
58
+ retry
59
+ end
60
+
61
+ puts
62
+ puts HTTP.get "#{url}/add?x=1&y=2"
63
+ puts HTTP.get "#{url}/add?x=3.14&y=10"
64
+ HTTP.get "#{url}/exit"
65
+ end
66
+
67
+ Process.waitall
@@ -0,0 +1,113 @@
1
+ # A tuple store (in-memory) that is optimized for (key_string, object) pairs.
2
+ # The object may be any serializable object (built up from numbers, booleans,
3
+ # nil, strings, hashes and arrays).
4
+ #
5
+ # Unlike in a key-value store, a given key_string may occur more than once.
6
+ # It is up to the application to decide whether to enforce key uniqueness or
7
+ # not (for example, by taking (k,...) before writing (k,v).
8
+ #
9
+ # This store should be used only by clients that subscribe to a subspace
10
+ # that can be represented as pairs. (See memo2.rb.)
11
+ #
12
+ # This store also manages meta tuples, which it keeps in an array, just like
13
+ # the default Tuplespace class does.
14
+ class KVSpace
15
+ include Enumerable
16
+
17
+ attr_reader :tag, :hash, :metas
18
+
19
+ def initialize tag
20
+ @tag = tag
21
+ clear
22
+ end
23
+
24
+ def clear
25
+ @hash = Hash.new {|h,k| h[k] = []}
26
+ # It's up to the application to enforce that these arrays have size <=1.
27
+ @metas = []
28
+ # We are automatically subscribed to tupelo metadata (subspace defs), so
29
+ # we need to keep them somewhere.
30
+ end
31
+
32
+ def each
33
+ hash.each do |k, vs|
34
+ vs.each do |v|
35
+ yield tag, k, v
36
+ end
37
+ end
38
+ metas.each do |tuple|
39
+ yield tuple
40
+ end
41
+ end
42
+
43
+ def insert tuple
44
+ if tuple.kind_of? Array
45
+ # and tuple.size == 3 and tuple[0] == tag and tuple[1].kind_of? String
46
+ # This is redundant, because of subscribe.
47
+ t, k, v = tuple
48
+ hash[k] << v
49
+
50
+ else
51
+ metas << tuple
52
+ end
53
+ end
54
+
55
+ def delete_once tuple
56
+ if tuple.kind_of? Array
57
+ # and tuple.size == 3 and tuple[0] == tag and tuple[1].kind_of? String
58
+ # This is redundant, because of subscribe.
59
+ t, k, v = tuple
60
+ if hash.key?(k) and hash[k].include? v
61
+ hash[k].delete v
62
+ hash.delete k if hash[k].empty?
63
+ true
64
+ else
65
+ false
66
+ end
67
+
68
+ else
69
+ if i=metas.index(tuple)
70
+ delete_at i
71
+ end
72
+ end
73
+ end
74
+
75
+ def transaction inserts: [], deletes: [], tick: nil
76
+ deletes.each do |tuple|
77
+ delete_once tuple or raise "bug"
78
+ end
79
+
80
+ inserts.each do |tuple|
81
+ insert tuple.freeze ## should be deep_freeze
82
+ end
83
+ end
84
+
85
+ def find_distinct_matches_for templates
86
+ templates.inject([]) do |tuples, template|
87
+ tuples << find_match_for(template, distinct_from: tuples)
88
+ end
89
+ end
90
+
91
+ def find_match_for template, distinct_from: []
92
+ # try to optimize if template can be satisfied by hash lookup
93
+ if template.kind_of? RubyObjectTemplate
94
+ spec = template.spec
95
+ if spec.kind_of? Array
96
+ key = spec[1]
97
+ if key.kind_of? String and spec[2] == nil
98
+ if hash.key? key
99
+ value = hash[key].last # most recently written
100
+ return [tag, key, value]
101
+ else
102
+ return nil
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ # fall back to linear search
109
+ find do |tuple|
110
+ template === tuple and not distinct_from.any? {|t| t.equal? tuple}
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,78 @@
1
+ # A tupelo cluster may expose its services using some other protocol so that
2
+ # process need not be tupelo-aware to use them. This example uses http to expose
3
+ # a memcache-like service. See also memo2.rb.
4
+ #
5
+ # Depends on the sinatra, json, and http gems.
6
+
7
+ require 'json'
8
+
9
+ fork do
10
+ require 'tupelo/app'
11
+
12
+ Tupelo.application do
13
+ child do |client|
14
+ require 'sinatra/base'
15
+
16
+ Class.new(Sinatra::Base).class_eval do
17
+ get '/' do
18
+ "hello, world\n"
19
+ end
20
+
21
+ get '/read' do
22
+ key = params["k"]
23
+ resp = client.read ["memo", key, nil]
24
+ resp.to_json + "\n"
25
+ end
26
+
27
+ get '/exit' do
28
+ Thread.new {sleep 1; exit}
29
+ "bye\n"
30
+ end
31
+
32
+ run!
33
+ end
34
+ end
35
+
36
+ # this process does some imaginary super-important work whose results
37
+ # need to be cached.
38
+ child passive: true do
39
+ 100.times do |i|
40
+ sleep 1
41
+ write ["memo", i.to_s, Time.now.to_s]
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ fork do # No tupelo in this process.
48
+ require 'http'
49
+
50
+ url = 'http://localhost:4567'
51
+
52
+ print "trying server at #{url}"
53
+ begin
54
+ print "."
55
+ HTTP.get url
56
+ rescue Errno::ECONNREFUSED
57
+ sleep 0.2
58
+ retry
59
+ end
60
+
61
+ puts
62
+ puts "Getting cached data as soon as available:"
63
+ 5.times do |i|
64
+ resp = HTTP.get "#{url}/read?k=#{i}"
65
+ p JSON.parse(resp)
66
+ end
67
+
68
+ puts
69
+ puts "Reviewing already cached data:"
70
+ 5.times do |i|
71
+ resp = HTTP.get "#{url}/read?k=#{i}"
72
+ p JSON.parse(resp)
73
+ end
74
+
75
+ HTTP.get "#{url}/exit"
76
+ end
77
+
78
+ Process.waitall
@@ -0,0 +1,94 @@
1
+ # Better, but more complex, implementation of memo.rb. Uses a custom tuplespace
2
+ # that is optimized for storing key-value data, rather than general tuples.
3
+ # Also, subscribes to just the relevant subspace. Consequently, this example
4
+ # should scale up to large memo spaces much better than memo.rb, which uses
5
+ # linear search.
6
+ #
7
+ # Depends on the sinatra, json, and http gems.
8
+
9
+ require 'json'
10
+
11
+ fork do
12
+ require 'tupelo/app'
13
+ require_relative 'kvspace.rb'
14
+
15
+ Tupelo.application do
16
+ local do
17
+ use_subspaces!
18
+
19
+ define_subspace(
20
+ tag: "memo",
21
+ template: [
22
+ {value: "memo"}, # tag is encoded in each tuple, for recognizing
23
+ {type: "string"}, # key in the cache, must be string
24
+ nil # value, can be any object (e.g. JSON object)
25
+ ]
26
+ )
27
+ end
28
+
29
+ child tuplespace: [KVSpace, "memo"], subscribe: ["memo"] do |client|
30
+ require 'sinatra/base'
31
+
32
+ Class.new(Sinatra::Base).class_eval do
33
+ get '/' do
34
+ "hello, world\n"
35
+ end
36
+
37
+ get '/read' do
38
+ key = params["k"]
39
+ resp = client.read ["memo", key, nil]
40
+ resp.to_json + "\n"
41
+ end
42
+
43
+ get '/exit' do
44
+ Thread.new {sleep 1; exit}
45
+ "bye\n"
46
+ end
47
+
48
+ run!
49
+ end
50
+ end
51
+
52
+ # this process does some imaginary super-important work whose results
53
+ # need to be cached.
54
+ child passive: true do
55
+ 100.times do |i|
56
+ sleep 1
57
+ write ["memo", i.to_s, Time.now.to_s]
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ fork do # No tupelo in this process.
64
+ require 'http'
65
+
66
+ url = 'http://localhost:4567'
67
+
68
+ print "trying server at #{url}"
69
+ begin
70
+ print "."
71
+ HTTP.get url
72
+ rescue Errno::ECONNREFUSED
73
+ sleep 0.2
74
+ retry
75
+ end
76
+
77
+ puts
78
+ puts "Getting cached data as soon as available:"
79
+ 5.times do |i|
80
+ resp = HTTP.get "#{url}/read?k=#{i}"
81
+ p JSON.parse(resp)
82
+ end
83
+
84
+ puts
85
+ puts "Reviewing already cached data:"
86
+ 5.times do |i|
87
+ resp = HTTP.get "#{url}/read?k=#{i}"
88
+ p JSON.parse(resp)
89
+ end
90
+
91
+ HTTP.get "#{url}/exit"
92
+ end
93
+
94
+ Process.waitall
data/example/pubsub.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # Synchronously deliver published messages to subscribers. Subscriber gets the
2
2
  # message only if waiting while the message is sent.
3
- # Compare message-bus.rb.
3
+ # Compare message-bus.rb. Also: subspaces/pubsub.rb.
4
4
  #
5
5
  # Run with the --show-final-state switch to verify that published tuples
6
6
  # don't stay in the space.
@@ -18,6 +18,7 @@ Tupelo.application do
18
18
 
19
19
  N_PUBS.times do |pi|
20
20
  child do
21
+ log.progname = "pub #{pi}"
21
22
  read ['start']
22
23
  delay = pi/10.0
23
24
  sleep delay
data/example/subspace.rb CHANGED
@@ -2,6 +2,7 @@ require 'tupelo/app'
2
2
 
3
3
  Tupelo.application do
4
4
  local do
5
+ log.progname = "before"
5
6
  log [subscribed_all, subscribed_tags]
6
7
 
7
8
  use_subspaces!
@@ -18,7 +19,13 @@ Tupelo.application do
18
19
  log read_all(Object)
19
20
  end
20
21
 
22
+ child subscribe: [], passive: true do
23
+ log.progname = "not a subscriber"
24
+ log "should never see this: #{read(subspace "foo")}"
25
+ end
26
+
21
27
  cid = child subscribe: ["foo"] do
28
+ log.progname = "foo subscriber"
22
29
  log [subscribed_all, subscribed_tags]
23
30
  write [1]
24
31
  write_wait ["abc"]
@@ -27,6 +34,7 @@ Tupelo.application do
27
34
  Process.wait cid
28
35
 
29
36
  local do
37
+ log.progname = "after"
30
38
  log [subscribed_all, subscribed_tags]
31
39
  log read_all(Object)
32
40
  log read_all(subspace "foo")
@@ -0,0 +1,77 @@
1
+ # Synchronously deliver published messages to subscribers. Subscriber gets the
2
+ # message only if waiting while the message is sent. Use subspaces to reduce
3
+ # total message traffic.
4
+ # Compare ../pubsub.rb.
5
+ #
6
+ # Run with the --show-final-state switch to verify that published tuples
7
+ # don't stay in the space.
8
+
9
+ require 'tupelo/app'
10
+
11
+ show_final_state = ARGV.delete "--show-final-state"
12
+
13
+ N_PUBS = 6
14
+ N_SUBS = 6
15
+ N_CHAN = 3
16
+
17
+ Tupelo.application do
18
+ local do
19
+ use_subspaces!
20
+
21
+ N_CHAN.times do |i|
22
+ define_subspace(
23
+ tag: i,
24
+ template: [
25
+ {value: i},
26
+ {type: "string"}
27
+ ]
28
+ )
29
+ end
30
+
31
+ define_subspace(
32
+ tag: "control",
33
+ template: [
34
+ {value: "control"},
35
+ nil
36
+ ]
37
+ )
38
+ end
39
+
40
+ N_PUBS.times do |pi|
41
+ child subscribe: ["control"] do
42
+ log.progname = "pub #{pi}"
43
+ read [nil, 'start'] # first elt can only be 'control'
44
+ delay = pi/10.0
45
+ sleep delay
46
+ tag = pi % N_CHAN
47
+ pulse [tag, "pub #{pi} slept for #{delay} sec"]
48
+ end
49
+ end
50
+
51
+ N_SUBS.times do |si|
52
+ tag = si % N_CHAN
53
+ child subscribe: [tag], passive: true do
54
+ log.progname = "sub #{si} on tag #{tag}"
55
+ loop do
56
+ log read [nil, nil]
57
+ # Note: match any pair, but in fact will only get [tag, ....]
58
+ # because of subspaces.
59
+ end
60
+ end
61
+ end
62
+
63
+ if show_final_state
64
+ child passive: true do
65
+ log.progname = "final"
66
+ def self.stop
67
+ log read_all
68
+ super
69
+ end
70
+ sleep
71
+ end
72
+ end
73
+
74
+ local do
75
+ write ['control', 'start']
76
+ end
77
+ end
data/lib/tupelo/app.rb CHANGED
@@ -95,7 +95,7 @@ module Tupelo
95
95
 
96
96
  %w{--marshal --yaml --json --msgpack}.each do |switch|
97
97
  s = argv.delete(switch) and
98
- otps[:blob_type] = s.delete("--")
98
+ opts[:blob_type] = s.delete("--")
99
99
  end
100
100
 
101
101
  opts[:trace] = argv.delete("--trace")
@@ -1,4 +1,6 @@
1
1
  class Tupelo::Archiver
2
+ # Faster than the default tuplespace, but does not support some things
3
+ # that are not needed in the Archiver: template searches, for example.
2
4
  class Tuplespace
3
5
  include Enumerable
4
6
 
@@ -106,6 +106,9 @@ class Tupelo::Client
106
106
  @tuplespace ||= begin
107
107
  if client.tuplespace.respond_to? :new
108
108
  client.tuplespace.new
109
+ elsif client.tuplespace.class == Array # but not subclass of Array
110
+ tsclass, *args = client.tuplespace
111
+ tsclass.new(*args)
109
112
  else
110
113
  client.tuplespace
111
114
  end
@@ -294,7 +297,7 @@ class Tupelo::Client
294
297
  update_to_tick tick: msg.global_tick, all: true
295
298
  when Funl::SUBSCRIBE
296
299
  update_to_tick tick: msg.global_tick,
297
- tags: (client.subscribed_tags + tags)
300
+ tags: (client.subscribed_tags | tags)
298
301
  when Funl::UNSUBSCRIBE_ALL
299
302
  update_to_tick tick: msg.global_tick, all: false
300
303
  when Funl::UNSUBSCRIBE
@@ -332,18 +335,28 @@ class Tupelo::Client
332
335
  read_tuples = op.reads.map {|t| tuplespace.find_match_for(t)}
333
336
 
334
337
  succeeded = !op.atomic || (granted_tuples.all? && read_tuples.all?)
335
- actual_tuples = granted_tuples.compact
338
+ take_tuples = granted_tuples.compact
339
+
340
+ if client.subscribed_all
341
+ write_tuples = op.writes
342
+ else
343
+ write_tuples = op.writes.select do |tuple|
344
+ subspaces.any? {|subspace| subspace === tuple}
345
+ end
346
+ end
347
+ ## This is duplicated effort: the sender has already done this.
348
+ ## So maybe the result could be transmitted in the msg protocol?
336
349
 
337
350
  if succeeded
338
- log.debug {"inserting #{op.writes}; deleting #{actual_tuples}"}
339
- tuplespace.transaction inserts: op.writes, deletes: actual_tuples,
351
+ log.debug {"inserting #{op.writes}; deleting #{take_tuples}"}
352
+ tuplespace.transaction inserts: write_tuples, deletes: take_tuples,
340
353
  tick: @global_tick
341
354
 
342
355
  op.writes.each do |tuple|
343
356
  sniff_meta_tuple tuple
344
357
  end
345
358
 
346
- actual_tuples.each do |tuple|
359
+ take_tuples.each do |tuple|
347
360
  ### abstract this out
348
361
  if tuple.kind_of? Hash and tuple.key? "__tupelo__"
349
362
  if tuple["__tupelo__"] == "subspace" # tuple is subspace metatdata
@@ -372,9 +385,9 @@ class Tupelo::Client
372
385
  log.debug {"operation belongs to this client: #{trans.inspect}"}
373
386
  end
374
387
 
375
- if not actual_tuples.empty?
388
+ if not take_tuples.empty?
376
389
  if succeeded
377
- actual_tuples.each do |tuple|
390
+ take_tuples.each do |tuple|
378
391
  prep_waiters.keep_if do |waiter|
379
392
  waiter.unprepare tuple
380
393
  ## optimization: track number of instances of tuple, to avoid
@@ -387,7 +400,7 @@ class Tupelo::Client
387
400
 
388
401
  else
389
402
  log.debug {
390
- missing = op.takes - actual_tuples
403
+ missing = op.takes - take_tuples
391
404
  trans ? "failed to take #{missing}" :
392
405
  "client #{msg.client_id} failed to take #{missing}"}
393
406
  end
@@ -420,7 +433,7 @@ class Tupelo::Client
420
433
  if succeeded
421
434
  trans.done msg.global_tick, granted_tuples # note: tuples not frozen
422
435
  else
423
- trans.fail (op.takes - actual_tuples) + (op.reads - read_tuples)
436
+ trans.fail (op.takes - take_tuples) + (op.reads - read_tuples)
424
437
  end
425
438
  end
426
439
  end
@@ -494,19 +507,27 @@ class Tupelo::Client
494
507
  end
495
508
 
496
509
  def handle_waiter waiter
497
- tuplespace.find {|tuple| waiter.gloms tuple} or
510
+ tuple = tuplespace.find_match_for waiter.template
511
+ if tuple
512
+ waiter.peek tuple
513
+ else
498
514
  read_waiters << waiter
499
- ## optimize: if template is just a tuple, use hashing,
500
- ## but will need to expose waiter.tuple
515
+ end
501
516
  end
502
517
 
503
518
  def handle_matcher matcher
504
519
  if matcher.all
505
520
  tuplespace.each {|tuple| matcher.gloms tuple}
521
+ ## maybe should have tuplespace.find_all_matches_for ...
522
+ ## in case there is an optimization
506
523
  matcher.fails
507
524
  else
508
- tuplespace.find {|tuple| matcher.gloms tuple} or
525
+ tuple = tuplespace.find_match_for waiter.template
526
+ if tuple
527
+ waiter.peek tuple
528
+ else
509
529
  matcher.fails
530
+ end
510
531
  end
511
532
  end
512
533
 
data/lib/tupelo/client.rb CHANGED
@@ -36,6 +36,12 @@ module Tupelo
36
36
  subscribe_all
37
37
  when Array
38
38
  subscribe @initial_subscriptions | [Tupelo::Client::TUPELO_SUBSPACE_TAG]
39
+ when String
40
+ @initial_subscriptions = [@initial_subscriptions]
41
+ subscribe @initial_subscriptions | [Tupelo::Client::TUPELO_SUBSPACE_TAG]
42
+ else
43
+ raise ArgumentError,
44
+ "bad subscription specifier: #{@initial_subscriptions}"
39
45
  end
40
46
  end
41
47
 
@@ -60,6 +66,7 @@ module Tupelo
60
66
  # call this just once at start of first client (it's optional to
61
67
  # preserve behavior of non-subspace-aware code)
62
68
  def use_subspaces!
69
+ return if subspace(TUPELO_SUBSPACE_TAG)
63
70
  define_subspace(
64
71
  tag: TUPELO_SUBSPACE_TAG,
65
72
  template: {
@@ -73,7 +80,7 @@ module Tupelo
73
80
 
74
81
  def subspace tag
75
82
  tag = tag.to_s
76
- worker.subspaces.find {|sp| sp.tag == tag}
83
+ worker.subspaces.find {|sp| sp.tag == tag} ## should go thru worker queue
77
84
  end
78
85
  end
79
86
  end
@@ -1,3 +1,3 @@
1
1
  module Tupelo
2
- VERSION = "0.10"
2
+ VERSION = "0.11"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tupelo
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.10'
4
+ version: '0.11'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel VanderWerf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-07 00:00:00.000000000 Z
11
+ date: 2013-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: atdo
@@ -113,7 +113,6 @@ files:
113
113
  - example/fail-and-retry.rb
114
114
  - example/load-balancer.rb
115
115
  - example/read-in-trans.rb
116
- - example/map-reduce.rb
117
116
  - example/bounded-retry.rb
118
117
  - example/pregel/remote.rb
119
118
  - example/pregel/pagerank.rb
@@ -122,7 +121,6 @@ files:
122
121
  - example/take-nowait.rb
123
122
  - example/boolean-match.rb
124
123
  - example/lease.rb
125
- - example/remote-map-reduce.rb
126
124
  - example/broker-locking.rb
127
125
  - example/transaction-logic.rb
128
126
  - example/message-bus.rb
@@ -138,18 +136,26 @@ files:
138
136
  - example/balance-xfer-retry.rb
139
137
  - example/custom-search.rb
140
138
  - example/app-and-tup.rb
139
+ - example/multi-tier/memo2.rb
140
+ - example/multi-tier/http.rb
141
+ - example/multi-tier/kvspace.rb
142
+ - example/multi-tier/memo.rb
143
+ - example/multi-tier/drb.rb
141
144
  - example/take-many.rb
145
+ - example/subspaces/pubsub.rb
142
146
  - example/dphil-optimistic.rb
143
147
  - example/async-transaction.rb
144
148
  - example/wait-interrupt.rb
145
149
  - example/zk/lock.rb
146
150
  - example/subspace.rb
147
- - example/map-reduce-v2.rb
148
151
  - example/deadlock.rb
149
152
  - example/add.rb
150
153
  - example/dphil-optimistic-v2.rb
151
154
  - example/parallel.rb
152
155
  - example/tiny-client.rb
156
+ - example/map-reduce/map-reduce.rb
157
+ - example/map-reduce/remote-map-reduce.rb
158
+ - example/map-reduce/map-reduce-v2.rb
153
159
  - example/lock-mgr-with-queue.rb
154
160
  - example/balance-xfer.rb
155
161
  - example/cancel.rb
@@ -202,7 +208,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
202
208
  version: '0'
203
209
  requirements: []
204
210
  rubyforge_project:
205
- rubygems_version: 2.1.10
211
+ rubygems_version: 2.1.11
206
212
  signing_key:
207
213
  specification_version: 4
208
214
  summary: Distributed tuplespace