the_lone_dyno 0.1.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +9 -6
- data/lib/the_lone_dyno.rb +21 -46
- data/lib/the_lone_dyno/version.rb +1 -1
- data/the_lone_dyno.gemspec +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: adca286b5e4342cdb298d996c5b0c3c4d0b3ed28
|
4
|
+
data.tar.gz: fd8ca87a0ea95f950d302adea2a98c9c7e73fd7a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f5765b377228c781d175eeaac583f698956fdc98cadb65aa3696b122811eb16043f0c7e181ca3e6db7f1a83435587553d1e65fdcba95fcfe274e87b9c8399ac
|
7
|
+
data.tar.gz: cdd2373747aa9e34b8325769495d8eab1fd22dc15251c9eeb58ce658b80d93b91886b9e12bf7ef5e4c8263326348e24f5148d51239b1aef6afbd5b6c3b748923
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# A Log of Changes!
|
2
2
|
|
3
|
+
## [1.0.0] - 2015-11-06
|
4
|
+
|
5
|
+
- Exclusivity is no longer provided by advisory locks instead uses a naming convention
|
6
|
+
provided by ENV['DYNO']. Prior implementation had the problem that on Puma or unicorn
|
7
|
+
the lock would be aquired by the master process but not the puma workers . Most likely we want
|
8
|
+
the workers to be doing tasks.
|
9
|
+
|
3
10
|
## [0.1.1] - 2015-10-12
|
4
11
|
|
5
12
|
- Restrict locking behavior by process type. Default is "web".
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# TheLoneDyno
|
4
4
|
|
5
|
-
Isolate code to only run on a certain number of Heroku dynos.
|
5
|
+
Isolate code to only run on a certain number of Heroku dynos. Then use [LISTEN/NOTIFY](http://www.postgresql.org/docs/9.1/static/sql-notify.html) to trigger custom events on those dynos.
|
6
6
|
|
7
7
|
[![Build Status](https://travis-ci.org/schneems/the_lone_dyno.svg?branch=master)](https://travis-ci.org/schneems/the_lone_dyno)
|
8
8
|
|
@@ -14,6 +14,10 @@ Why is this needed? All Heroku dynos operate independently of one another. Once
|
|
14
14
|
|
15
15
|
> Be warned, only changing behavior on 1 dyno in your app, could cause difficult to reproduce problems. "I'm getting an error, but only once fore every 20 requests". Using this library is an advanced technique and should be implemented with care. Make sure to tell the rest of your team what you're doing, and remove the code from your codebase as soon as you're done.
|
16
16
|
|
17
|
+
## How
|
18
|
+
|
19
|
+
A big change happened between version 0.1 and 1.0. We are no longer using Postgres advisory locks. Instead we're depending on the `DYNO=web.1` environment variables. This makes behavior more predictable and easier to reason about.
|
20
|
+
|
17
21
|
## Installation
|
18
22
|
|
19
23
|
Add this line to your application's Gemfile:
|
@@ -115,7 +119,7 @@ TheLoneDyno.exclusive(process_type: "worker") do
|
|
115
119
|
end
|
116
120
|
```
|
117
121
|
|
118
|
-
This will attempt to lock any DYNO environment variable containing "worker" string.
|
122
|
+
This will attempt to lock any DYNO environment variable containing "worker" string.
|
119
123
|
|
120
124
|
## Running on more than 1 Dyno
|
121
125
|
|
@@ -129,7 +133,7 @@ end
|
|
129
133
|
|
130
134
|
## Using multiple locks on the same app
|
131
135
|
|
132
|
-
|
136
|
+
If you need to customize the default listen key, for instance if you want to have multiple processes you want to isolate to a set of dynos you can use the `key_base:` key, for example:
|
133
137
|
|
134
138
|
```ruby
|
135
139
|
TheLoneDyno.exclusive(key_base: "reticulate splines") do
|
@@ -145,7 +149,6 @@ end
|
|
145
149
|
|
146
150
|
If you don't want to run your exclusive process in the background you can force it to run syncronously using `background: false`. For example:
|
147
151
|
|
148
|
-
|
149
152
|
```ruby
|
150
153
|
TheLoneDyno.exclusive(background: false) do |signal|
|
151
154
|
while true do
|
@@ -175,7 +178,7 @@ Keep in mind that your lock is only held for the duration of your block, so if t
|
|
175
178
|
|
176
179
|
## Connection
|
177
180
|
|
178
|
-
By default The Lone Dyno assumes you're using Active Record and already have a connection configured. If you want to use a different ORM, you'll need to provide TheLoneDyno with an object that responds to `exec` that executes arbitrary SQL. Under the hood this library uses [
|
181
|
+
By default The Lone Dyno assumes you're using Active Record and already have a connection configured. If you want to use a different ORM, you'll need to provide TheLoneDyno with an object that responds to `exec` that executes arbitrary SQL. Under the hood this library uses [hey_you](https://github.com/heroku/hey_you#database-connection). So that's how you can configure the connection
|
179
182
|
|
180
183
|
```
|
181
184
|
connection = Module do
|
@@ -192,7 +195,7 @@ end
|
|
192
195
|
You can alternatively set the `DEFAULT_CONNECTION`
|
193
196
|
|
194
197
|
```
|
195
|
-
|
198
|
+
HeyYou::DEFAULT_CONNECTION = Module do
|
196
199
|
def self.exec(sql, bind)
|
197
200
|
DB.fetch(sql, bind)
|
198
201
|
end
|
data/lib/the_lone_dyno.rb
CHANGED
@@ -1,76 +1,51 @@
|
|
1
1
|
require "the_lone_dyno/version"
|
2
|
-
require "
|
2
|
+
require "hey_you"
|
3
3
|
|
4
4
|
module TheLoneDyno
|
5
5
|
DEFAULT_KEY = "the_lone_dyno_hi_ho_silver"
|
6
|
-
|
6
|
+
DEFAULT_PROCESS_TYPE = "web"
|
7
7
|
|
8
8
|
# Use to ensure only `dynos` count of dynos are exclusively running
|
9
9
|
# the given block
|
10
|
-
def self.exclusive(
|
10
|
+
def self.exclusive(dynos: 1, process_type: DEFAULT_PROCESS_TYPE, background: true, sleep: 60, ttl: 0.1, connection: ::HeyYou::DEFAULT_CONNECTION_CONNECTOR.call, key_base: DEFAULT_KEY, **args, &block)
|
11
|
+
dynos = dyno_names(dynos, process_type)
|
11
12
|
|
12
|
-
return
|
13
|
+
return unless dynos.include?(ENV['DYNO'])
|
14
|
+
|
15
|
+
watcher = ListenWatch.new(ENV['DYNO'] + key_base, connection)
|
13
16
|
|
14
17
|
if background
|
15
18
|
Thread.new do
|
16
|
-
forever_block = Proc.new { |*
|
17
|
-
|
19
|
+
forever_block = Proc.new { |*a| block.call(*a); while true do; sleep 180 ; end; }
|
20
|
+
forever_block.call(watcher)
|
18
21
|
end
|
19
22
|
else
|
20
|
-
|
23
|
+
block.call(watcher)
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
27
|
+
def self.dyno_names(dynos, process_type)
|
28
|
+
1.upto(dynos.to_i).map { |i| "#{process_type}.#{i}" }
|
29
|
+
end
|
30
|
+
|
24
31
|
# Use to send a custom signal to any exclusive running dynos
|
25
|
-
def self.signal(payload = "", dynos: 1, key_base: DEFAULT_KEY, connection: ::
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
connection.
|
32
|
+
def self.signal(payload = "", dynos: 1, process_type: DEFAULT_PROCESS_TYPE, key_base: DEFAULT_KEY, connection: ::HeyYou::DEFAULT_CONNECTION_CONNECTOR.call, **args, &block)
|
33
|
+
dynos = dyno_names(dynos, process_type)
|
34
|
+
|
35
|
+
dynos.each do |dyno|
|
36
|
+
HeyYou.new(channel: (dyno + key_base).gsub(".".freeze, "_".freeze), connection: connection).notify(payload)
|
30
37
|
end
|
31
38
|
end
|
32
39
|
|
33
40
|
# Used for running a block when a pg NOTIFY is sent
|
34
41
|
class ListenWatch
|
35
42
|
def initialize(key, raw_connection)
|
36
|
-
@key = key
|
43
|
+
@key = key.gsub(".".freeze, "_".freeze)
|
37
44
|
@raw_connection = raw_connection
|
38
45
|
end
|
39
46
|
|
40
47
|
def watch(sleep: 60, ttl: 0.01, &block)
|
41
|
-
|
42
|
-
|
43
|
-
@thread = Thread.new do
|
44
|
-
while true do
|
45
|
-
sleep sleep
|
46
|
-
|
47
|
-
@raw_connection.wait_for_notify(ttl) do |channel, pid, payload|
|
48
|
-
block.call(payload)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Used for generating lock and listen keys. Isolates
|
56
|
-
# advisory locking behavior.
|
57
|
-
class Lock
|
58
|
-
def initialize(key_base, dynos, &block)
|
59
|
-
@key_base = key_base.to_s
|
60
|
-
@dynos = Integer(dynos)
|
61
|
-
@block = block
|
62
|
-
end
|
63
|
-
|
64
|
-
def keys
|
65
|
-
@dynos.times.map {|i| "#{@key_base}_#{i}" }
|
66
|
-
end
|
67
|
-
|
68
|
-
def lock(connection, &block)
|
69
|
-
keys.each do |key|
|
70
|
-
PgLock.new(name: key, ttl: false, connection: connection).lock do
|
71
|
-
block.call(ListenWatch.new(key, connection))
|
72
|
-
end
|
73
|
-
end
|
48
|
+
HeyYou.new(sleep: sleep, ttl: ttl, channel: @key, connection: @raw_connection).listen(&block)
|
74
49
|
end
|
75
50
|
end
|
76
51
|
end
|
data/the_lone_dyno.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
-
spec.add_dependency "
|
23
|
+
spec.add_dependency "hey_you", "~> 0.1.1"
|
24
24
|
|
25
25
|
spec.add_development_dependency "pg", ">= 0.15"
|
26
26
|
spec.add_development_dependency "activerecord", ">= 2.3"
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: the_lone_dyno
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- schneems
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: hey_you
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.1.
|
19
|
+
version: 0.1.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.1.
|
26
|
+
version: 0.1.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: pg
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|