tupelo 0.17 → 0.18

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: c5703dd8bef3eca3c30fc208f1ff5fe291b19b4a
4
- data.tar.gz: c84e02596fc16e71dce360a8ba67b7033194d15b
3
+ metadata.gz: 60516b564c0329a1544a913f4ec1680bd8308de8
4
+ data.tar.gz: 6aadbcc4c96e57d762d072e266ff921972f58578
5
5
  SHA512:
6
- metadata.gz: f6e2afa9ccf4cea0e12497ae4b8d98d169d2b8b6b46dcf92ff13d46f44d111080331fc80aa637f9234ef41d3727cd7e591d7e861bf2492bce5fda9ab258e6e32
7
- data.tar.gz: afb4669e2bdf49294eda7423b250ea62f0e4efedd7f9ea250658ee531e76871d27d027b456492680d0fa920ffd9d5045fb6f7df569bdc52945f7677b86ffb325
6
+ metadata.gz: 75b24e537a78c831be55b389457a41fd527bf3fc88cb7e4dd358d03ecc492d439157ce28a3191ae86946c2df12a5ee5f74b5b388aebd8a9da57e0e1cc0af8386
7
+ data.tar.gz: 53a0848f5b3c30e091fc0bd0d58fced4703b77561099866b032e1d9f7c312a85da01d2a4066c2979f2632caa31a6c97466b21cf4319acca99308f4beddaa25d5
data/COPYING CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013, Joel VanderWerf
1
+ Copyright (c) 2013-2014, Joel VanderWerf
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Tupelo
2
2
  ==
3
3
 
4
- A tuplespace that is fast, scalable, and language agnostic. It is designed for distribution of both computation and storage (disk and memory), in a unified language that has both transactional and tuple-operation (read/write/take) semantics.
4
+ A tuplespace that is fast, scalable, and language agnostic. Tupelo is designed for distribution of both computation and storage (disk and memory), in a unified language that has both transactional and tuple-operation (read/write/take) semantics.
5
5
 
6
6
  This is the reference implementation in ruby. It should be able to communicate with implementations in other languages.
7
7
 
@@ -12,7 +12,10 @@ Documentation
12
12
  * [FAQ](doc/faq.md)
13
13
  * [Comparisons](doc/compare.md)
14
14
  * [Transactions](doc/transactions.md)
15
+ * [Replication](doc/replication.md)
15
16
  * [Subspaces](doc/subspace.md)
17
+ * [Causality](doc/causality.md)
18
+ * [Concurrency](doc/concurrency.md)
16
19
  * [Examples](example/)
17
20
 
18
21
  Internals
@@ -41,14 +44,13 @@ Getting started
41
44
  >> t [nil, nil]
42
45
  => ["hello", "world"]
43
46
 
44
- 4. Take a look at the [FAQ](doc/faq.md), [tutorial](doc/tutorial.md), and many (examples)(example/).
47
+ 4. Take a look at the [FAQ](doc/faq.md), [tutorial](doc/tutorial.md), and the many [examples](example/).
45
48
 
46
49
 
47
50
  Applications
48
51
  =======
49
52
 
50
- Tupelo is a flexible base layer for various distributed programming paradigms: job queues, dataflow, map-reduce, etc.
51
-
53
+ Tupelo is a flexible base layer for various distributed programming paradigms: job queues, dataflow, map-reduce, etc. Using subspaces, it's also a transactional, replicated datastore with pluggable storage providers.
52
54
 
53
55
 
54
56
  Advantages
@@ -148,6 +150,6 @@ Joel VanderWerf, vjoel@users.sourceforge.net, @JoelVanderWerf.
148
150
  License and Copyright
149
151
  ========
150
152
 
151
- Copyright (c) 2013, Joel VanderWerf
153
+ Copyright (c) 2013-2014, Joel VanderWerf
152
154
 
153
155
  License for this project is BSD. See the COPYING file for the standard BSD license. The supporting gems developed for this project are similarly licensed.
@@ -0,0 +1,30 @@
1
+ # Benchmark the default tuplespace (which is not intended to be fast).
2
+
3
+ module Tupelo
4
+ class Client; end
5
+ end
6
+
7
+ require 'tupelo/client/tuplespace'
8
+ require 'benchmark'
9
+
10
+ N_TUPLES = 100_000
11
+ N_DELETES = 10_000
12
+
13
+ Benchmark.bm(20) do |b|
14
+ ts = Tupelo::Client::SimpleTuplespace.new
15
+
16
+ b.report('insert') do
17
+ N_TUPLES.times do |i|
18
+ ts.insert i
19
+ end
20
+ end
21
+
22
+ b.report('delete') do
23
+ N_DELETES.times do
24
+ i = rand(N_TUPLES)
25
+ if ts.delete_once i
26
+ ts.insert i
27
+ end
28
+ end
29
+ end
30
+ end
data/bin/tup CHANGED
@@ -119,12 +119,28 @@ Tupelo.application(
119
119
  CMD_ALIASES = %w{ w pl t r ra tr }
120
120
  private *CMD_ALIASES
121
121
 
122
+ def trace *args
123
+ require 'tupelo/app/trace'
124
+ if args.empty?
125
+ if tracing? then stop_trace else start_trace end
126
+ elsif args.first
127
+ start_trace
128
+ else
129
+ stop_trace
130
+ end
131
+ tracing?
132
+ end
133
+
122
134
  def help
123
135
  puts "Command aliases:"
124
136
  CMD_ALIASES.each do |m_name|
125
137
  m = method(m_name)
126
138
  printf "%8s -> %s\n", m.name, m.original_name
127
139
  end
140
+ puts
141
+ puts "Extra commands:"
142
+ puts " trace [true|false] # turn on or off tracing"
143
+ puts " trace # toggle tracing"
128
144
  nil
129
145
  end
130
146
  end
@@ -0,0 +1,29 @@
1
+ # Unlike MPI barriers, does not use token ring. This is better for loosely
2
+ # coupled systems -- a failed process doesn't break the ring, the state is in
3
+ # the distributed tuplespace rather than just one process, and a replacement
4
+ # process can be swapped in. Run with --trace to see what's happening.
5
+
6
+ require 'tupelo/app'
7
+
8
+ N_WORKERS = 5
9
+ N_STEPS = 3
10
+
11
+ Tupelo.application do
12
+ pids = []
13
+ N_WORKERS.times do |wi|
14
+ pids << child do
15
+ N_STEPS.times do |si|
16
+ sleep 0.1 # do some work
17
+
18
+ write step: si, worker: wi
19
+
20
+ N_WORKERS.times do |i|
21
+ read step: si, worker: i
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ pids.each {|pid| Process.wait pid}
28
+ puts "done"
29
+ end
@@ -0,0 +1,42 @@
1
+ require 'tupelo/app'
2
+
3
+ N_BUYERS = 3
4
+ MAX_PER_BUYER = 4
5
+
6
+ Tupelo.application do
7
+ child do
8
+ # stock up on cards to sell
9
+ 10.times do |i|
10
+ write ["card", i] # todo add card details
11
+ end
12
+
13
+ # start selling
14
+ write ["selling", true]
15
+ sleep 1
16
+
17
+ # stop selling
18
+ take ["selling", true]
19
+ write ["selling", false]
20
+
21
+ # run the game
22
+ p read_all ["player", nil, "card", nil]
23
+ end
24
+
25
+ N_BUYERS.times do
26
+ child do
27
+ catch :done_buying do
28
+ MAX_PER_BUYER.times do
29
+ transaction do
30
+ _, selling = read ["selling", nil]
31
+ if selling
32
+ _, card_id = take ["card", nil]
33
+ write ["player", client_id, "card", card_id]
34
+ else
35
+ throw :done_buying
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -15,7 +15,8 @@ Tupelo.application do
15
15
 
16
16
  N_ITER.times do
17
17
  transaction do
18
- # lock the resource (in transaction, so optimistically):
18
+ # lock the resource (in transaction, so optimistically)
19
+ # this is really just an assertion of facts:
19
20
  read ["chopstick", i]
20
21
  read ["chopstick", (i+1)%N_PHIL]
21
22
 
@@ -1,7 +1,10 @@
1
1
  # Modified prime-factor.rb attempts to balance load better. Improvement
2
2
  # varies--typically around 50% faster with 12 remote hosts.
3
+ # The key to balancing load in this case is that all tuples can be
4
+ # read in each worker, so the worker doesn't have to guess so much.
3
5
 
4
6
  require 'tupelo/app/remote'
7
+ require 'set'
5
8
 
6
9
  hosts = ARGV.shift or abort "usage: #$0 <ssh-hostname>,<ssh-hostname>,..."
7
10
  hosts = hosts.split(",")
@@ -13,7 +16,7 @@ Tupelo.tcp_application do
13
16
  class M
14
17
  def initialize nh, hi, excl = []
15
18
  @nh, @hi = nh, hi
16
- @excl = excl
19
+ @excl = Set.new(excl)
17
20
  end
18
21
  def === x
19
22
  Array === x and
@@ -37,7 +40,7 @@ Tupelo.tcp_application do
37
40
 
38
41
  if input
39
42
  begin
40
- txn.commit
43
+ txn.commit # but don't wait yet. Optimistically start working...
41
44
  output = input.prime_division
42
45
  Thread.new do
43
46
  begin
@@ -51,6 +54,9 @@ Tupelo.tcp_application do
51
54
  rescue TransactionFailure
52
55
  end
53
56
  my_pref = my_pref.exclude input
57
+ # Some other worker is optimistically working on this item
58
+ # (stolen from this worker), so don't try to take it again.
59
+ # Reduces contention.
54
60
  next
55
61
  end
56
62
 
@@ -60,27 +66,18 @@ Tupelo.tcp_application do
60
66
  end
61
67
  break
62
68
  end
63
-
69
+
70
+ # No more preferred items, so fall back to dumb (and contentious) algo.
64
71
  loop do
65
72
  _, input = take(["input", Integer])
66
73
  write ["output", input, input.prime_division]
67
74
  end
68
-
69
-
70
- # _, input =
71
- # begin
72
- # take(my_pref, timeout: 1.0) # fewer fails (5.0 -> none at all)
73
- # rescue TimeoutError
74
- # take(["input", Integer])
75
- # end
76
- # write ["output", input, input.prime_division]
77
- # end
78
75
  }
79
76
  end
80
77
 
81
78
  local do
82
79
  t0 = Time.now
83
- inputs = 1_000_000_000_000 .. 1_000_000_000_050
80
+ inputs = 1_000_000_000_000 .. 1_000_000_000_200
84
81
 
85
82
  inputs.each do |input|
86
83
  write ["input", input]
@@ -20,7 +20,7 @@ Tupelo.tcp_application do
20
20
 
21
21
  local do
22
22
  t0 = Time.now
23
- inputs = 1_000_000_000_000 .. 1_000_000_000_050
23
+ inputs = 1_000_000_000_000 .. 1_000_000_000_200
24
24
 
25
25
  inputs.each do |input|
26
26
  write ["input", input]
data/example/nest.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'tupelo/app'
2
+
3
+ Tupelo.application do
4
+
5
+ child do
6
+ write [1]
7
+
8
+ transaction timeout: 1 do |outer|
9
+ x, _ = outer.read [1]
10
+
11
+ transaction do
12
+ write [x+1]
13
+ # This is a programming error. The outer txn assumes that [1] exists.
14
+ # The inner txn assumes that the outer txn executes. But in fact the
15
+ # outer txn never executes (it times out), and the inner txn does
16
+ # execute, leaving an unexpected [2] in the space.
17
+ end
18
+
19
+ outer.read ["barrier"]
20
+ end
21
+ end
22
+
23
+ child do
24
+ take [1]
25
+ write ["barrier"]
26
+ end
27
+ end
@@ -0,0 +1,55 @@
1
+ # An implementation of the observer pattern using transaction failure.
2
+ # Note that the failures happen in the pre-commit phase of the transactions,
3
+ # so they will not be shown with the --trace switch.
4
+ # Run with `--stream` to show the optional observer implemented using
5
+ # streaming reads instead of transaction failure.
6
+
7
+ require 'tupelo/app'
8
+
9
+ Tupelo.application do
10
+
11
+ child do
12
+ log.progname = "counting client"
13
+
14
+ write count: 0
15
+
16
+ 3.times do
17
+ sleep 1
18
+ transaction do
19
+ count = take(count: Numeric)["count"]
20
+ write count: count + 1
21
+ log "incrementing counter"
22
+ end
23
+ end
24
+ end
25
+
26
+ child passive: true do
27
+ log.progname = "observing client"
28
+
29
+ loop do
30
+ begin
31
+ t = transaction
32
+ counter = t.read count: Numeric
33
+ log "entering new state: #{counter}"
34
+ t.wait
35
+ rescue Tupelo::Client::TransactionFailure => ex
36
+ log "leaving old state: #{counter}"
37
+ end
38
+ end
39
+ end
40
+
41
+ if argv.include?("--stream")
42
+ # If you only care about seeing each new values as it arrives, this client
43
+ # is enough. But it doesn't alert you when a tuple is deleted. In this
44
+ # example, the delete and insert happen atomically in a transaction, so
45
+ # it doesn't matter.
46
+ child passive: true do
47
+ log.progname = "stream client"
48
+
49
+ read count: Numeric do |counter|
50
+ log "entering new state: #{counter}"
51
+ end
52
+ end
53
+ end
54
+
55
+ 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
@@ -119,7 +119,7 @@ Tupelo.application do
119
119
  name = "Daisy"
120
120
  write [cmd_tag, rqid, "fetch", [name]]
121
121
  addr = take( [resp_tag, rqid, nil, nil, nil] ).last
122
- log "found: #{name} => #{addr}"
122
+ log "Looked up #{name} and found: #{name} => #{addr}"
123
123
 
124
124
  rqid = next_rqid.call
125
125
  write [cmd_tag, rqid, "first", []]
@@ -0,0 +1,103 @@
1
+ # A toy implementation of Riemann (http://riemann.io)
2
+ #
3
+ # Also, this is an example of storing a subspace using different data
4
+ # structures in different clients, depending on needs: some clients (generic
5
+ # consumers) need to index by host and service, and others (expiration manager)
6
+ # need to sort by expiration time.
7
+
8
+ require 'tupelo/app'
9
+
10
+ N_PRODUCERS = 3
11
+ N_CONSUMERS = 2
12
+
13
+ Tupelo.application do
14
+
15
+ local do
16
+ use_subspaces!
17
+
18
+ define_subspace("event", {
19
+ # field type description
20
+ # (from http://riemann.io/concepts.html)
21
+
22
+ host: String, # A hostname, e.g. "api1", "foo.com"
23
+
24
+ service: String, # e.g. "API port 8000 reqs/sec"
25
+
26
+ state: String, # Any string less than 255 bytes, e.g. "ok",
27
+ # "warning", "critical"
28
+
29
+ time: Numeric, # The time of the event, in unix epoch seconds
30
+
31
+ description: String, # Freeform text
32
+
33
+ tags: Array, # Freeform list of strings,
34
+ # e.g. ["rate", "fooproduct", "transient"]
35
+
36
+ metric: Numeric, # A number associated with this event,
37
+ # e.g. the number of reqs/sec.
38
+
39
+ ttl: Numeric # A floating-point time, in seconds, that this
40
+ # event is considered valid for. Expired states
41
+ # may be removed from the index.
42
+ })
43
+ end
44
+
45
+ N_PRODUCERS.times do
46
+ child subscribe: [] do # N.b., no subscriptions
47
+ event = {
48
+ host: `hostname`.chomp,
49
+ service: "service #{client_id}", # placeholder
50
+ state: "",
51
+ time: 0,
52
+ description: "",
53
+ tags: [],
54
+ metric: 0,
55
+ ttl: 0
56
+ }.freeze
57
+
58
+ e_ok = event.merge(
59
+ state: "ok",
60
+ time: Time.now.to_f,
61
+ ttl: 1.0
62
+ )
63
+
64
+ if e_ok[:ttl] == 0.0
65
+ pulse e_ok # no need to bother with expiration
66
+ else
67
+ write e_ok
68
+ end
69
+ end
70
+ end
71
+
72
+ N_CONSUMERS.times do
73
+ # stores events indexed by host, service
74
+ child subscribe: "event", passive: true do ### tuplespace: sqlite
75
+ read subspace("event") do |event|
76
+ log event ### need filtering, actions, etc.
77
+ end
78
+ end
79
+ end
80
+
81
+ # This could be a subspace of the event subspace.
82
+ critical_event = {
83
+ host: nil,
84
+ service: nil,
85
+ state: /critical|fatal/i,
86
+ time: nil,
87
+ description: nil,
88
+ tags: nil,
89
+ metric: nil,
90
+ ttl: nil
91
+ }
92
+
93
+ child subscribe: "event", passive: true do
94
+ read critical_event do |event|
95
+ log.error event
96
+ end
97
+ end
98
+
99
+ # expirer: stores current events in expiration order
100
+ child subscribe: "event", passive: true do
101
+ ### use rbtree
102
+ end
103
+ end
@@ -0,0 +1,130 @@
1
+ require 'rbtree'
2
+
3
+ class SortedSetTemplate
4
+ class << self
5
+ alias [] new
6
+ end
7
+
8
+ # cmd can be "next", "prev", "first", "last"
9
+ # for next/prev, args is ["name"]
10
+ # for first/last, args is empty
11
+ def initialize tag, cmd, *args
12
+ @tag = tag
13
+ @cmd = cmd
14
+ @args = args
15
+ end
16
+
17
+ def === other
18
+ raise ### should not need this?
19
+ end
20
+
21
+ def find_in rbtree
22
+ case @cmd
23
+ when "first"
24
+ rbtree.first
25
+ ###
26
+ end
27
+ end
28
+
29
+ # A tuple store (in-memory) that is optimized for (key_string, object) pairs.
30
+ # The object may be any serializable object (built up from numbers, booleans,
31
+ # nil, strings, hashes and arrays).
32
+ #
33
+ # Unlike in a key-value store, a given key_string may occur more than once.
34
+ # It is up to the application to decide whether to enforce key uniqueness or
35
+ # not (for example, by taking (k,...) before writing (k,v).
36
+ #
37
+ # This store should be used only by clients that subscribe to a subspace
38
+ # that can be represented as pairs. (See memo2.rb.)
39
+ #
40
+ # This store also manages meta tuples, which it keeps in an array, just like
41
+ # the default Tuplespace class does.
42
+ class SortedSetSpace
43
+ include Enumerable
44
+
45
+ attr_reader :tag, :hash, :metas
46
+
47
+ def initialize tag
48
+ @tag = tag
49
+ clear
50
+ end
51
+
52
+ def clear
53
+ @hash = Hash.new {|h,k| h[k] = []}
54
+ # It's up to the application to enforce that these arrays have size <=1.
55
+ @metas = []
56
+ # We are automatically subscribed to tupelo metadata (subspace defs), so
57
+ # we need to keep them somewhere.
58
+ end
59
+
60
+ def each
61
+ hash.each do |k, vs|
62
+ vs.each do |v|
63
+ yield tag, k, v
64
+ end
65
+ end
66
+ metas.each do |tuple|
67
+ yield tuple
68
+ end
69
+ end
70
+
71
+ def insert tuple
72
+ if tuple.kind_of? Array
73
+ # and tuple.size == 3 and tuple[0] == tag and tuple[1].kind_of? String
74
+ # This is redundant, because of subscribe.
75
+ t, k, v = tuple
76
+ hash[k] << v
77
+
78
+ else
79
+ metas << tuple
80
+ end
81
+ end
82
+
83
+ def delete_once tuple
84
+ if tuple.kind_of? Array
85
+ # and tuple.size == 3 and tuple[0] == tag and tuple[1].kind_of? String
86
+ # This is redundant, because of subscribe.
87
+ t, k, v = tuple
88
+ if hash.key?(k) and hash[k].include? v
89
+ hash[k].delete v
90
+ hash.delete k if hash[k].empty?
91
+ true
92
+ else
93
+ false
94
+ end
95
+
96
+ else
97
+ if i=metas.index(tuple)
98
+ delete_at i
99
+ end
100
+ end
101
+ end
102
+
103
+ def transaction inserts: [], deletes: [], tick: nil
104
+ deletes.each do |tuple|
105
+ delete_once tuple or raise "bug"
106
+ end
107
+
108
+ inserts.each do |tuple|
109
+ insert tuple.freeze ## should be deep_freeze
110
+ end
111
+ end
112
+
113
+ def find_distinct_matches_for templates
114
+ templates.inject([]) do |tuples, template|
115
+ tuples << find_match_for(template, distinct_from: tuples)
116
+ end
117
+ end
118
+
119
+ def find_match_for template, distinct_from: []
120
+ case template
121
+ when SortedSetTemplate
122
+ template.find_in rbtree, distinct_from: distinct_from ###
123
+ else
124
+ # fall back to linear search
125
+ find do |tuple|
126
+ template === tuple and not distinct_from.any? {|t| t.equal? tuple}
127
+ end
128
+ end
129
+ end
130
+ end
@@ -1,27 +1,58 @@
1
1
  require 'tupelo/app'
2
2
 
3
+ class Tupelo::Client
4
+ def trace_loop
5
+ note = notifier
6
+ log << ( "%6s %6s %6s %s\n" % %w{ tick cid status operation } )
7
+ loop do
8
+ status, tick, cid, op, tags = note.wait
9
+ unless status == :attempt
10
+ s = status == :failure ? "FAIL" : ""
11
+ if tags and not tags.empty?
12
+ log << ( "%6d %6d %6s %p to %p\n" % [tick, cid, s, op, tags] )
13
+ else
14
+ log << ( "%6d %6d %6s %p\n" % [tick, cid, s, op] )
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ def tracing?
21
+ !!@trace_thread
22
+ end
23
+
24
+ private
25
+
26
+ # Turn on tracing in this client (performed by a thread).
27
+ def start_trace
28
+ @trace_thread ||= Thread.new do
29
+ begin
30
+ trace_loop
31
+ rescue => ex
32
+ log "trace thread: #{ex}"
33
+ end
34
+ end
35
+ end
36
+
37
+ # Turn off tracing in this client.
38
+ def stop_trace
39
+ if @trace_thread
40
+ @trace_thread.kill
41
+ @trace_thread = nil
42
+ end
43
+ end
44
+ end
45
+
3
46
  # displays every transaction in sequence, specially marking failed ones,
4
47
  # until INT signal
5
48
  class Tupelo::AppBuilder
6
49
  def start_trace
7
- child passive: true do |client|
50
+ child passive: true do
8
51
  trap :INT do
9
52
  exit!
10
53
  end
11
54
 
12
- note = client.notifier
13
- log << ( "%6s %6s %6s %s\n" % %w{ tick cid status operation } )
14
- loop do
15
- status, tick, cid, op, tags = note.wait
16
- unless status == :attempt
17
- s = status == :failure ? "FAIL" : ""
18
- if tags and not tags.empty?
19
- log << ( "%6d %6d %6s %p to %p\n" % [tick, cid, s, op, tags] )
20
- else
21
- log << ( "%6d %6d %6s %p\n" % [tick, cid, s, op] )
22
- end
23
- end
24
- end
55
+ trace_loop
25
56
  end
26
57
  end
27
58
  end
data/lib/tupelo/app.rb CHANGED
@@ -67,8 +67,12 @@ module Tupelo
67
67
  tunnel_default = !!opts[:tunnel]
68
68
  persist_dir = opts[:persist_dir]
69
69
 
70
+ if not services_file and argv[0] !~ /^-/ ## hacky
71
+ services_file = argv.shift
72
+ end
73
+
70
74
  ez_opts = {
71
- services_file: services_file || argv.shift,
75
+ services_file: services_file,
72
76
  interactive: $stdin.isatty
73
77
  }
74
78
 
@@ -2,7 +2,7 @@ class Tupelo::Client
2
2
  module Api
3
3
  def define_subspace tag, template, addr: nil
4
4
  metatuple = {
5
- __tupelo__: "subspace",
5
+ TUPELO_META_KEY => "subspace",
6
6
  tag: tag,
7
7
  template: PortableObjectTemplate.spec_from(template),
8
8
  addr: addr
@@ -15,7 +15,7 @@ class Tupelo::Client
15
15
  def use_subspaces!
16
16
  return if subspace(TUPELO_SUBSPACE_TAG)
17
17
  define_subspace(TUPELO_SUBSPACE_TAG, {
18
- __tupelo__: "subspace",
18
+ TUPELO_META_KEY => "subspace",
19
19
  tag: nil,
20
20
  template: nil,
21
21
  addr: nil
@@ -26,7 +26,10 @@ class Tupelo::Client
26
26
  tag = tag.to_s
27
27
  worker.subspaces.find {|sp| sp.tag == tag} or begin
28
28
  if subscribed_tags.include? tag
29
- read __tupelo__: "subspace", tag: tag, addr: nil, template: nil
29
+ read TUPELO_META_KEY => "subspace",
30
+ tag: tag,
31
+ template: nil,
32
+ addr: nil
30
33
  worker.subspaces.find {|sp| sp.tag == tag}
31
34
  end
32
35
  end
@@ -211,9 +211,19 @@ class Tupelo::Client
211
211
  end
212
212
  end
213
213
 
214
+ def check_open
215
+ if failed?
216
+ # checking this here is mostly a courtesy to client code; it is possible
217
+ # (a benign race condition) for the failure flag to be set later,
218
+ # even while a #write or #take method still has not returned.
219
+ raise exception
220
+ elsif not open?
221
+ raise TransactionStateError, "not open: #{inspect}"
222
+ end
223
+ end
224
+
214
225
  def write *tuples
215
- raise TransactionStateError, "not open: #{inspect}" unless open? or
216
- failed?
226
+ check_open
217
227
  check_tuples tuples
218
228
  blobber = worker.blobber
219
229
  @writes.concat tuples.map {|t| blobber.load(blobber.dump(t))}
@@ -223,9 +233,7 @@ class Tupelo::Client
223
233
  end
224
234
 
225
235
  def pulse *tuples
226
- raise exception if failed?
227
- raise TransactionStateError, "not open: #{inspect}" unless open? or
228
- failed?
236
+ check_open
229
237
  check_tuples tuples
230
238
  blobber = worker.blobber
231
239
  @pulses.concat tuples.map {|t| blobber.load(blobber.dump(t))}
@@ -234,9 +242,7 @@ class Tupelo::Client
234
242
 
235
243
  # raises TransactionFailure
236
244
  def take template_spec
237
- raise exception if failed?
238
- raise TransactionStateError, "not open: #{inspect}" unless open? or
239
- failed?
245
+ check_open
240
246
  template = worker.make_template(template_spec)
241
247
  @take_templates << template
242
248
  log.debug {"asking worker to take #{template_spec.inspect}"}
@@ -246,9 +252,7 @@ class Tupelo::Client
246
252
  end
247
253
 
248
254
  def take_nowait template_spec
249
- raise exception if failed?
250
- raise TransactionStateError, "not open: #{inspect}" unless open? or
251
- failed?
255
+ check_open
252
256
  template = worker.make_template(template_spec)
253
257
  @_take_nowait ||= {}
254
258
  i = @take_templates.size
@@ -262,9 +266,7 @@ class Tupelo::Client
262
266
 
263
267
  # transaction applies only if template has a match
264
268
  def read template_spec
265
- raise exception if failed?
266
- raise TransactionStateError, "not open: #{inspect}" unless open? or
267
- failed?
269
+ check_open
268
270
  template = worker.make_template(template_spec)
269
271
  @read_templates << template
270
272
  log.debug {"asking worker to read #{template_spec.inspect}"}
@@ -274,9 +276,7 @@ class Tupelo::Client
274
276
  end
275
277
 
276
278
  def read_nowait template_spec
277
- raise exception if failed?
278
- raise TransactionStateError, "not open: #{inspect}" unless open? or
279
- failed?
279
+ check_open
280
280
  template = worker.make_template(template_spec)
281
281
  @_read_nowait ||= {}
282
282
  i = @read_templates.size
@@ -249,9 +249,9 @@ class Tupelo::Client
249
249
 
250
250
  begin
251
251
  tuplespace.clear
252
- ## in some cases, we can keep some of it, but the current
253
- ## archiver is not smart enough to send exactly the delta
254
- ### abort all current transactions???
252
+ ## In some cases, we can keep some of it, but the current
253
+ ## archiver is not smart enough to send exactly the delta.
254
+ ## Also, might need to abort some current transactions.
255
255
 
256
256
  arc_tick = arc.read[0]
257
257
  log.info "arc says global_tick = #{arc_tick}"
@@ -434,8 +434,8 @@ class Tupelo::Client
434
434
 
435
435
  # Returns true if tuple is subspace metadata.
436
436
  def is_meta_tuple? tuple
437
- tuple.kind_of? Hash and tuple.key? "__tupelo__" and
438
- tuple["__tupelo__"] == "subspace"
437
+ tuple.kind_of? Hash and tuple.key? TUPELO_META_KEY and
438
+ tuple[TUPELO_META_KEY] == "subspace"
439
439
  end
440
440
 
441
441
  def sniff_meta_tuple tuple
data/lib/tupelo/client.rb CHANGED
@@ -11,13 +11,14 @@ module Tupelo
11
11
  attr_reader :worker
12
12
  attr_reader :tuplespace
13
13
 
14
- TUPELO_SUBSPACE_TAG = "tupelo subspace"
14
+ TUPELO_SUBSPACE_TAG = "tupelo subspace".freeze
15
+ TUPELO_META_KEY = "__tupelo__".freeze
15
16
 
16
17
  def initialize(tuplespace: SimpleTuplespace, subscribe: :all, **opts)
17
18
  super **opts
18
19
  @tuplespace = tuplespace
19
20
  @worker = make_worker
20
- @initial_subscriptions = subscribe
21
+ @initial_subscriptions = subscribe || []
21
22
  end
22
23
 
23
24
  def inspect
@@ -1,3 +1,3 @@
1
1
  module Tupelo
2
- VERSION = "0.17"
2
+ VERSION = "0.18"
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.17'
4
+ version: '0.18'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel VanderWerf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-18 00:00:00.000000000 Z
11
+ date: 2014-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: atdo
@@ -81,6 +81,7 @@ files:
81
81
  - README.md
82
82
  - Rakefile
83
83
  - bench/pipeline.rb
84
+ - bench/tuplespace.rb
84
85
  - bin/tspy
85
86
  - bin/tup
86
87
  - bugs/read-take.rb
@@ -92,6 +93,8 @@ files:
92
93
  - example/balance-xfer-locking.rb
93
94
  - example/balance-xfer-retry.rb
94
95
  - example/balance-xfer.rb
96
+ - example/barrier.rb
97
+ - example/bingo/bingo-v1.rb
95
98
  - example/boolean-match.rb
96
99
  - example/bounded-retry.rb
97
100
  - example/broker-locking.rb
@@ -128,7 +131,9 @@ files:
128
131
  - example/multi-tier/memo.rb
129
132
  - example/multi-tier/memo2.rb
130
133
  - example/multi-tier/multi-sinatras.rb
134
+ - example/nest.rb
131
135
  - example/notify.rb
136
+ - example/observer.rb
132
137
  - example/optimist.rb
133
138
  - example/parallel.rb
134
139
  - example/pregel/distributed.rb
@@ -143,13 +148,16 @@ files:
143
148
  - example/small-simplified.rb
144
149
  - example/small.rb
145
150
  - example/socket-broker.rb
151
+ - example/subspaces/addr-book-v1.rb
146
152
  - example/subspaces/addr-book-v2.rb
147
153
  - example/subspaces/addr-book.rb
148
154
  - example/subspaces/pubsub.rb
149
155
  - example/subspaces/ramp.rb
156
+ - example/subspaces/riemann.rb
150
157
  - example/subspaces/shop/shop-v1.rb
151
158
  - example/subspaces/shop/shop-v2.rb
152
159
  - example/subspaces/simple.rb
160
+ - example/subspaces/sorted-set-space-OLD.rb
153
161
  - example/subspaces/sorted-set-space.rb
154
162
  - example/take-many.rb
155
163
  - example/take-nowait-caution.rb
@@ -231,7 +239,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
231
239
  version: '0'
232
240
  requirements: []
233
241
  rubyforge_project:
234
- rubygems_version: 2.2.1
242
+ rubygems_version: 2.2.2
235
243
  signing_key:
236
244
  specification_version: 4
237
245
  summary: Distributed tuplespace