noeq 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +13 -0
- data/TODO +2 -0
- data/lib/noeq.rb +73 -8
- data/noeq.gemspec +1 -1
- data/test/noeq_test.rb +89 -0
- metadata +6 -3
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
noeq-rb is a [noeqd](https://github.com/bmizerany/noeqd) GUID client in Ruby.
|
4
4
|
|
5
|
+
[Annotated source code is available](http://titanous.com/noeq-rb/).
|
6
|
+
|
5
7
|
## Installation
|
6
8
|
|
7
9
|
```
|
@@ -26,3 +28,14 @@ noeq = Noeq.new('idserver.local')
|
|
26
28
|
noeq.generate #=> 142692638036852736
|
27
29
|
noeq.generate(5) #=> [142692782450933760, 142692782450933761, 142692782450933762, 142692782450933763, 142692782450933764]
|
28
30
|
```
|
31
|
+
|
32
|
+
### Async usage
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 'noeq'
|
36
|
+
|
37
|
+
noeq = Noeq.new('localhost', 4444, :async => true)
|
38
|
+
noeq.request_id
|
39
|
+
# do some things
|
40
|
+
noeq.fetch_id #=> 142692638036852736
|
41
|
+
```
|
data/TODO
ADDED
data/lib/noeq.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
+
# **Noeq** generates GUIDs using [noeqd](https://github.com/bmizerany/noeqd).
|
2
|
+
|
3
|
+
# `noeqd` uses a simple TCP wire protocol, so let's require our only dependency,
|
4
|
+
# `socket`.
|
1
5
|
require 'socket'
|
2
6
|
|
3
7
|
class Noeq
|
8
|
+
|
9
|
+
# If you just want to test out `noeq` or need to use it in a one-off script,
|
10
|
+
# this method allows for very simple usage.
|
4
11
|
def self.generate(n=1)
|
5
12
|
noeq = new
|
6
13
|
ids = noeq.generate(n)
|
@@ -8,36 +15,94 @@ class Noeq
|
|
8
15
|
ids
|
9
16
|
end
|
10
17
|
|
11
|
-
|
12
|
-
|
18
|
+
# `Noeq.new` defaults to connecting to `localhost:4444` with async off.
|
19
|
+
# The `options` hash is used so that we are verbose when turning async on.
|
20
|
+
def initialize(host = 'localhost', port = 4444, options = {})
|
21
|
+
@host, @port, @async = host, port, options[:async]
|
13
22
|
connect
|
14
23
|
end
|
15
24
|
|
25
|
+
# The first thing that we need to do is connect to the `noeqd` server.
|
16
26
|
def connect
|
17
|
-
|
27
|
+
# We create a new TCP `STREAM` socket. There are a few other types of
|
28
|
+
# sockets, but this is the most common.
|
29
|
+
@socket = Socket.new(:INET, :STREAM)
|
30
|
+
|
31
|
+
# In order to create a socket connection we need an address object.
|
32
|
+
address = Socket.sockaddr_in(@port, @host)
|
33
|
+
|
34
|
+
# If async is enabled, we establish the connection in nonblocking mode,
|
35
|
+
# otherwise we connect normally, which will wait until the connection is
|
36
|
+
# established.
|
37
|
+
@async ? @socket.connect_nonblock(address) : @socket.connect(address)
|
38
|
+
|
39
|
+
# `Socket.connect_nonblock` raises `Errno::EINPROGRESS` if the socket isn't
|
40
|
+
# connected instantly. It will be connected in the background, so we ignore
|
41
|
+
# the exception
|
42
|
+
rescue Errno::EINPROGRESS
|
18
43
|
end
|
19
44
|
|
20
45
|
def disconnect
|
46
|
+
# If the socket has already been closed by the other side, `close` will
|
47
|
+
# raise, so we rescue it.
|
21
48
|
@socket.close rescue false
|
22
49
|
end
|
23
50
|
|
51
|
+
# The workhorse generate method. Defaults to one id, but up to 255 can be
|
52
|
+
# requested.
|
24
53
|
def generate(n=1)
|
25
|
-
|
26
|
-
|
27
|
-
|
54
|
+
request_id(n)
|
55
|
+
fetch_id(n)
|
56
|
+
|
57
|
+
# If something goes wrong, we reconnect and retry. There is a slim chance
|
58
|
+
# that this will result in an infinite loop, but most errors are raised in
|
59
|
+
# the reconnect step and won't get re-rescued here.
|
28
60
|
rescue
|
29
61
|
disconnect
|
30
62
|
connect
|
31
63
|
retry
|
32
64
|
end
|
33
65
|
|
66
|
+
def request_id(n=1)
|
67
|
+
# The integer is packed into a binary byte and sent to the `noeqd` server.
|
68
|
+
# The second argument to `BasicSocket#send` is a bitmask of flags, we don't
|
69
|
+
# need anything special, so it is set to zero.
|
70
|
+
@socket.send [n].pack('c'), 0
|
71
|
+
end
|
72
|
+
alias :request_ids :request_id
|
73
|
+
|
74
|
+
def fetch_id(n=1)
|
75
|
+
# We collect the ids from the `noeqd` server.
|
76
|
+
ids = (1..n).map { get_id }.compact
|
77
|
+
|
78
|
+
# If we have more than one id, we return the array, otherwise we return the
|
79
|
+
# single id.
|
80
|
+
ids.length > 1 ? ids : ids.first
|
81
|
+
end
|
82
|
+
alias :fetch_ids :fetch_id
|
83
|
+
|
34
84
|
private
|
35
85
|
|
36
86
|
def get_id
|
37
|
-
|
87
|
+
# `noeqd` sends us a 64-bit unsigned integer in network (big-endian) byte
|
88
|
+
# order, but Ruby 1.8 doesn't have a native unpack directive for this, so we
|
89
|
+
# do it manually by shifting the high bits and adding the low bits.
|
90
|
+
high, low = read_long, read_long
|
91
|
+
return unless high && low
|
92
|
+
(high << 32) + low
|
38
93
|
end
|
39
94
|
|
40
95
|
def read_long
|
41
|
-
|
96
|
+
# `IO.select` blocks until one of the sockets passed in has an event
|
97
|
+
# or a timeout is reached (the fourth argument). We don't do the `select`
|
98
|
+
# if we are in async mode.
|
99
|
+
IO.select([@socket], nil, nil, 0.1) unless @async
|
100
|
+
|
101
|
+
# Since `select` has already blocked for us, we are pretty sure that
|
102
|
+
# there is data available on the socket, so we try to fetch 4 bytes and
|
103
|
+
# unpack them as a 32-bit big-endian unsigned integer. If there is no data
|
104
|
+
# available this will raise `Errno::EAGAIN` which will propagate up and
|
105
|
+
# could cause a retry.
|
106
|
+
@socket.recv_nonblock(4).unpack("N").first
|
42
107
|
end
|
43
108
|
end
|
data/noeq.gemspec
CHANGED
data/test/noeq_test.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require './lib/noeq'
|
3
|
+
|
4
|
+
class NoeqTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
FakeNoeqd.start
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
FakeNoeqd.stop
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_simple_generate
|
15
|
+
assert_equal expected_id, Noeq.generate
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_multiple_generate
|
19
|
+
noeq = Noeq.new
|
20
|
+
assert_equal [expected_id]*3, noeq.generate(3)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_different_port
|
24
|
+
FakeNoeqd.stop
|
25
|
+
FakeNoeqd.start(4545)
|
26
|
+
|
27
|
+
noeq = Noeq.new('localhost', 4545)
|
28
|
+
assert_equal expected_id, noeq.generate
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_reconnect
|
32
|
+
noeq = Noeq.new
|
33
|
+
assert noeq.generate
|
34
|
+
|
35
|
+
FakeNoeqd.stop
|
36
|
+
FakeNoeqd.start
|
37
|
+
|
38
|
+
assert_equal expected_id, noeq.generate
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_async_generate
|
42
|
+
noeq = Noeq.new('localhost', 4444, :async => true)
|
43
|
+
noeq.request_id
|
44
|
+
sleep 0.0001
|
45
|
+
assert_equal expected_id, noeq.fetch_id
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_async_request_with_disconnected_server_raises
|
49
|
+
noeq = Noeq.new('localhost', 4444, :async => true)
|
50
|
+
FakeNoeqd.stop
|
51
|
+
assert_raises(Errno::EPIPE) { noeq.request_id }
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def expected_id
|
57
|
+
144897448664367104
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
class FakeNoeqd
|
63
|
+
|
64
|
+
def self.start(port = 4444)
|
65
|
+
@server = new(port)
|
66
|
+
Thread.new { @server.accept_connections }
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.stop
|
70
|
+
@server.stop
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(port)
|
74
|
+
@socket = TCPServer.new(port)
|
75
|
+
end
|
76
|
+
|
77
|
+
def stop
|
78
|
+
@socket.close rescue true
|
79
|
+
end
|
80
|
+
|
81
|
+
def accept_connections
|
82
|
+
while conn = @socket.accept
|
83
|
+
while n = conn.read(1)
|
84
|
+
conn.send "\x02\x02\xC7v<\x80\x00\x00" * n.unpack('c')[0], 0
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: noeq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-12-
|
12
|
+
date: 2011-12-09 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Ruby noeqd GUID client
|
15
15
|
email:
|
@@ -22,8 +22,10 @@ files:
|
|
22
22
|
- LICENSE
|
23
23
|
- README.md
|
24
24
|
- Rakefile
|
25
|
+
- TODO
|
25
26
|
- lib/noeq.rb
|
26
27
|
- noeq.gemspec
|
28
|
+
- test/noeq_test.rb
|
27
29
|
homepage: http://github.com/titanous/noeq-rb
|
28
30
|
licenses: []
|
29
31
|
post_install_message:
|
@@ -48,4 +50,5 @@ rubygems_version: 1.8.11
|
|
48
50
|
signing_key:
|
49
51
|
specification_version: 3
|
50
52
|
summary: Ruby noeqd GUID client
|
51
|
-
test_files:
|
53
|
+
test_files:
|
54
|
+
- test/noeq_test.rb
|