fwd 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +3 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +11 -11
- data/benchmark/performance.rb +5 -7
- data/fwd.gemspec +1 -1
- data/lib/fwd.rb +23 -31
- data/lib/fwd/backend.rb +2 -2
- data/lib/fwd/buffer.rb +19 -6
- data/lib/fwd/cli.rb +6 -2
- data/lib/fwd/output.rb +25 -28
- data/spec/fwd/buffer_spec.rb +18 -3
- data/spec/fwd/cli_spec.rb +4 -0
- data/spec/fwd/input_spec.rb +1 -1
- data/spec/fwd/output_spec.rb +31 -24
- data/spec/fwd_spec.rb +1 -0
- data/spec/spec_helper.rb +2 -3
- metadata +3 -2
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
source
|
1
|
+
source "https://rubygems.org"
|
2
2
|
gemspec
|
data/Gemfile.lock
CHANGED
@@ -7,22 +7,22 @@ PATH
|
|
7
7
|
servolux
|
8
8
|
|
9
9
|
GEM
|
10
|
-
remote:
|
10
|
+
remote: https://rubygems.org/
|
11
11
|
specs:
|
12
12
|
connection_pool (1.0.0)
|
13
|
-
diff-lcs (1.1
|
13
|
+
diff-lcs (1.2.1)
|
14
14
|
eventmachine-le (1.1.4)
|
15
15
|
rake (10.0.3)
|
16
|
-
rspec (2.
|
17
|
-
rspec-core (~> 2.
|
18
|
-
rspec-expectations (~> 2.
|
19
|
-
rspec-mocks (~> 2.
|
20
|
-
rspec-core (2.
|
21
|
-
rspec-expectations (2.
|
22
|
-
diff-lcs (
|
23
|
-
rspec-mocks (2.
|
16
|
+
rspec (2.13.0)
|
17
|
+
rspec-core (~> 2.13.0)
|
18
|
+
rspec-expectations (~> 2.13.0)
|
19
|
+
rspec-mocks (~> 2.13.0)
|
20
|
+
rspec-core (2.13.0)
|
21
|
+
rspec-expectations (2.13.0)
|
22
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
23
|
+
rspec-mocks (2.13.0)
|
24
24
|
servolux (0.10.0)
|
25
|
-
yard (0.8.
|
25
|
+
yard (0.8.5.2)
|
26
26
|
|
27
27
|
PLATFORMS
|
28
28
|
ruby
|
data/benchmark/performance.rb
CHANGED
@@ -13,16 +13,14 @@ FileUtils.rm_rf TMP
|
|
13
13
|
FileUtils.mkdir_p TMP
|
14
14
|
FileUtils.touch(OUT)
|
15
15
|
|
16
|
-
FWD
|
17
|
-
NCC
|
18
|
-
|
19
|
-
sleep(5)
|
20
|
-
|
16
|
+
FWD = spawn "#{root}/bin/fwd-rb --flush 10000:2 -F tcp://0.0.0.0:7291 --path #{TMP} -v"
|
17
|
+
NCC = spawn "nc -kl 7291", out: [OUT, "w"]
|
21
18
|
EVENTS = 10_000_000
|
22
|
-
|
23
|
-
|
19
|
+
DATA = (("A".."Z").to_a + ("a".."z").to_a).join + "\n"
|
20
|
+
LENGTH = DATA.size
|
24
21
|
CCUR = 5
|
25
22
|
|
23
|
+
sleep(5)
|
26
24
|
ds = Benchmark.realtime do
|
27
25
|
(1..CCUR).map do
|
28
26
|
fork do
|
data/fwd.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.name = File.basename(__FILE__, '.gemspec')
|
10
10
|
s.summary = "fwd >>"
|
11
11
|
s.description = "The minimalistic stream forwarder"
|
12
|
-
s.version = "0.
|
12
|
+
s.version = "0.4.0"
|
13
13
|
|
14
14
|
s.authors = ["Black Square Media"]
|
15
15
|
s.email = "info@blacksquaremedia.com"
|
data/lib/fwd.rb
CHANGED
@@ -11,19 +11,6 @@ require 'servolux'
|
|
11
11
|
class Fwd
|
12
12
|
FLUSH = "\000>>"
|
13
13
|
|
14
|
-
class << self
|
15
|
-
|
16
|
-
attr_writer :logger
|
17
|
-
|
18
|
-
# [Logger] logger instance
|
19
|
-
def logger
|
20
|
-
@logger ||= ::Logger.new(STDOUT).tap do |l|
|
21
|
-
l.level = ::Logger::INFO
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
14
|
# @attr_reader [URI] uri to bind to
|
28
15
|
attr_reader :bind
|
29
16
|
|
@@ -36,6 +23,9 @@ class Fwd
|
|
36
23
|
# @attr_reader [Fwd::Output] output
|
37
24
|
attr_reader :output
|
38
25
|
|
26
|
+
# @attr_reader [Logger] logger
|
27
|
+
attr_reader :logger
|
28
|
+
|
39
29
|
# @attr_reader [Hash] opts
|
40
30
|
attr_reader :opts
|
41
31
|
|
@@ -49,18 +39,19 @@ class Fwd
|
|
49
39
|
# @option opts [Integer] flush_rate flush after N messages
|
50
40
|
# @option opts [Integer] flush_interval flush after N seconds
|
51
41
|
def initialize(opts = {})
|
42
|
+
@opts = opts
|
52
43
|
@bind = URI.parse(opts[:bind] || "tcp://0.0.0.0:7289")
|
53
44
|
@root = Pathname.new(opts[:path] || "tmp")
|
54
45
|
@prefix = opts[:prefix] || "buffer"
|
55
|
-
@
|
46
|
+
@logger = ::Logger.new(opts[:log] || STDOUT)
|
47
|
+
@logger.level = opts[:log_level] || ::Logger::INFO
|
56
48
|
@output = Fwd::Output.new(self)
|
57
49
|
end
|
58
50
|
|
59
51
|
# Starts the loop
|
60
52
|
def run!
|
61
|
-
$0 = "fwd-rb (output)"
|
62
|
-
|
63
53
|
@piper = ::Servolux::Piper.new('rw')
|
54
|
+
|
64
55
|
at_exit do
|
65
56
|
@piper.signal("TERM")
|
66
57
|
end
|
@@ -71,16 +62,8 @@ class Fwd
|
|
71
62
|
end
|
72
63
|
|
73
64
|
@piper.parent do
|
74
|
-
|
75
|
-
|
76
|
-
case val = @piper.gets()
|
77
|
-
when FLUSH
|
78
|
-
output.forward!
|
79
|
-
else
|
80
|
-
logger.error "Received unknown message #{val.class.name} "
|
81
|
-
exit
|
82
|
-
end
|
83
|
-
end
|
65
|
+
$0 = "fwd-rb (output)"
|
66
|
+
output_loop!
|
84
67
|
end
|
85
68
|
end
|
86
69
|
|
@@ -91,6 +74,20 @@ class Fwd
|
|
91
74
|
EM.start_server @bind.host, @bind.port, Fwd::Input, self, buffer
|
92
75
|
end
|
93
76
|
|
77
|
+
# Starts the output loop
|
78
|
+
def output_loop!
|
79
|
+
loop do
|
80
|
+
sleep(0.1)
|
81
|
+
case val = @piper.gets()
|
82
|
+
when FLUSH
|
83
|
+
@output.forward!
|
84
|
+
else
|
85
|
+
logger.error "Received unknown message #{val.class.name}: #{val.inspect}"
|
86
|
+
exit
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
94
91
|
# Initiates flush
|
95
92
|
def flush!
|
96
93
|
@piper.child do
|
@@ -98,11 +95,6 @@ class Fwd
|
|
98
95
|
end
|
99
96
|
end
|
100
97
|
|
101
|
-
# [Logger] logger instance
|
102
|
-
def logger
|
103
|
-
self.class.logger
|
104
|
-
end
|
105
|
-
|
106
98
|
end
|
107
99
|
|
108
100
|
%w|buffer output backend input pool cli|.each do |name|
|
data/lib/fwd/backend.rb
CHANGED
data/lib/fwd/buffer.rb
CHANGED
@@ -13,12 +13,15 @@ class Fwd::Buffer
|
|
13
13
|
@rate = (core.opts[:flush_rate] || 10_000).to_i
|
14
14
|
@limit = [core.opts[:buffer_limit].to_i, MAX_LIMIT].reject(&:zero?).min
|
15
15
|
@count = 0
|
16
|
+
cleanup!
|
16
17
|
reschedule!
|
18
|
+
|
19
|
+
at_exit { rotate! }
|
17
20
|
end
|
18
21
|
|
19
22
|
# @param [String] data binary data
|
20
23
|
def concat(data)
|
21
|
-
rotate! if
|
24
|
+
rotate! if limit_reached?
|
22
25
|
@fd.write(data)
|
23
26
|
@count += 1
|
24
27
|
flush! if flush?
|
@@ -43,8 +46,8 @@ class Fwd::Buffer
|
|
43
46
|
return if @fd && @fd.size.zero?
|
44
47
|
|
45
48
|
if @fd
|
46
|
-
|
47
|
-
|
49
|
+
close(@fd.path)
|
50
|
+
logger.debug { "Rotated #{File.basename(@fd.path)}, #{@fd.size / 1024}k" }
|
48
51
|
end
|
49
52
|
|
50
53
|
@fd = new_file
|
@@ -52,8 +55,8 @@ class Fwd::Buffer
|
|
52
55
|
logger.warn "Rotation delayed: #{e.message}"
|
53
56
|
end
|
54
57
|
|
55
|
-
# @return [Boolean] true if
|
56
|
-
def
|
58
|
+
# @return [Boolean] true if limit reached
|
59
|
+
def limit_reached?
|
57
60
|
@fd.nil? || @fd.size >= @limit
|
58
61
|
rescue Errno::ENOENT
|
59
62
|
false
|
@@ -72,6 +75,16 @@ class Fwd::Buffer
|
|
72
75
|
file
|
73
76
|
end
|
74
77
|
|
78
|
+
def close(file)
|
79
|
+
FileUtils.mv(file, file.sub(/\.open$/, ".closed"))
|
80
|
+
end
|
81
|
+
|
82
|
+
def cleanup!
|
83
|
+
Dir[root.join("#{prefix}.*.open")].each do |file|
|
84
|
+
File.size(file).zero? ? File.unlink(file) : close(file)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
75
88
|
def reschedule!
|
76
89
|
return unless @interval > 0
|
77
90
|
|
@@ -80,7 +93,7 @@ class Fwd::Buffer
|
|
80
93
|
end
|
81
94
|
|
82
95
|
def generate_name
|
83
|
-
[prefix, Time.now.utc.
|
96
|
+
[prefix, (Time.now.utc.to_f * 1000).round, SecureRandom.hex(2)].join(".")
|
84
97
|
end
|
85
98
|
|
86
99
|
end
|
data/lib/fwd/cli.rb
CHANGED
@@ -48,8 +48,12 @@ class Fwd::CLI < Hash
|
|
48
48
|
update prefix: prefix
|
49
49
|
end
|
50
50
|
|
51
|
-
o.on("-
|
52
|
-
|
51
|
+
o.on("-l", "--log PATH", "Custom log path. Default: STDOUT") do |path|
|
52
|
+
update log: path
|
53
|
+
end
|
54
|
+
|
55
|
+
o.on("-v", "--verbose", "Enable verbose logging.") do
|
56
|
+
update log_level: ::Logger::DEBUG
|
53
57
|
end
|
54
58
|
|
55
59
|
o.separator ""
|
data/lib/fwd/output.rb
CHANGED
@@ -2,7 +2,6 @@ class Fwd::Output
|
|
2
2
|
extend Forwardable
|
3
3
|
def_delegators :core, :logger, :root, :prefix
|
4
4
|
|
5
|
-
CHUNK_SIZE = 16 * 1024
|
6
5
|
RESCUABLE = [
|
7
6
|
Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EHOSTUNREACH, Errno::EPIPE,
|
8
7
|
Errno::ENETUNREACH, Errno::ENETDOWN, Errno::EINVAL, Errno::ETIMEDOUT,
|
@@ -26,23 +25,24 @@ class Fwd::Output
|
|
26
25
|
return if @forwarding
|
27
26
|
|
28
27
|
@forwarding = true
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
28
|
+
while (q = closed_files) && (file = q.shift)
|
29
|
+
ok = reserve(file) do |reserved|
|
30
|
+
start = Time.now
|
31
|
+
success = stream_file(reserved)
|
32
|
+
real = Time.now - start
|
33
|
+
logger.info { "Flushed #{reserved.basename}, #{reserved.size.fdiv(1024).round}k in #{real.round(1)}s (Q: #{q.size})" }
|
34
|
+
success
|
36
35
|
end
|
37
|
-
|
38
|
-
@forwarding = false
|
36
|
+
ok || break
|
39
37
|
end
|
38
|
+
ensure
|
39
|
+
@forwarding = false
|
40
40
|
end
|
41
41
|
|
42
|
-
# @param [
|
43
|
-
def
|
42
|
+
# @param [Pathname] file file to stream
|
43
|
+
def stream_file(file)
|
44
44
|
pool.any? do |backend|
|
45
|
-
|
45
|
+
stream_to(backend, file)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -54,29 +54,22 @@ class Fwd::Output
|
|
54
54
|
target = Pathname.new(file.sub(/\.closed$/, ".reserved"))
|
55
55
|
FileUtils.mv file, target.to_s
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
result = yield(io)
|
60
|
-
end
|
61
|
-
|
62
|
-
if result
|
57
|
+
success = yield(target)
|
58
|
+
if success
|
63
59
|
target.unlink
|
64
60
|
else
|
65
|
-
logger.error "Flushing
|
66
|
-
FileUtils.mv target.to_s,
|
61
|
+
logger.error "Flushing #{File.basename(file)} failed"
|
62
|
+
FileUtils.mv target.to_s, file
|
67
63
|
end
|
68
64
|
|
69
|
-
|
65
|
+
success
|
70
66
|
rescue Errno::ENOENT => e
|
71
67
|
# Ignore if file was alread flushed by another process
|
72
|
-
logger.warn "Flushing
|
68
|
+
logger.warn "Flushing #{File.basename(file)} postponed: #{e.message}"
|
73
69
|
end
|
74
70
|
|
75
|
-
def
|
76
|
-
|
77
|
-
until io.eof?
|
78
|
-
backend.write(io.read(CHUNK_SIZE))
|
79
|
-
end
|
71
|
+
def stream_to(backend, file)
|
72
|
+
backend.stream(file)
|
80
73
|
true
|
81
74
|
rescue *RESCUABLE => e
|
82
75
|
logger.error "Backend #{backend} failed: #{e.class.name} #{e.message}"
|
@@ -84,4 +77,8 @@ class Fwd::Output
|
|
84
77
|
false
|
85
78
|
end
|
86
79
|
|
80
|
+
def closed_files
|
81
|
+
Dir[root.join("#{prefix}.*.closed")].sort
|
82
|
+
end
|
83
|
+
|
87
84
|
end
|
data/spec/fwd/buffer_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Fwd::Buffer do
|
4
4
|
|
5
5
|
def files(glob = "*")
|
6
|
-
Dir[root.join(glob)]
|
6
|
+
Dir[root.join(glob)].map {|f| File.basename(f) }.sort
|
7
7
|
end
|
8
8
|
|
9
9
|
let(:buffer) { described_class.new core }
|
@@ -18,7 +18,22 @@ describe Fwd::Buffer do
|
|
18
18
|
its(:limit) { should be(2048) }
|
19
19
|
its(:timer) { should be(timer) }
|
20
20
|
its(:fd) { should be_nil }
|
21
|
-
its(:logger) { should be(
|
21
|
+
its(:logger) { should be(core.logger) }
|
22
|
+
|
23
|
+
it 'should clean up existing files' do
|
24
|
+
FileUtils.mkdir_p(root.to_s)
|
25
|
+
f1, f2 = "buffer.0.blank.open", "buffer.0.filled.open"
|
26
|
+
root.join(f1).open("wb") {}
|
27
|
+
root.join(f2).open("wb") {|f| f << "A" }
|
28
|
+
lambda { subject }.should change { files }.from([f1, f2]).to(["buffer.0.filled.closed"])
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should generate unique buffer file names' do
|
32
|
+
Time.stub now: Time.at(1313131313.2345678)
|
33
|
+
SecureRandom.stub hex: "6a7b8c9d"
|
34
|
+
buffer.concat("x")
|
35
|
+
buffer.fd.path.should == root.join("buffer.1313131313235.6a7b8c9d.open").to_s
|
36
|
+
end
|
22
37
|
|
23
38
|
describe "concat" do
|
24
39
|
it 'should concat data' do
|
@@ -51,7 +66,7 @@ describe Fwd::Buffer do
|
|
51
66
|
it { should change { files.size }.by(1) }
|
52
67
|
|
53
68
|
it 'should archive previous file' do
|
54
|
-
previous = buffer.fd.path
|
69
|
+
previous = File.basename(buffer.fd.path)
|
55
70
|
subject.call
|
56
71
|
files.should include(previous.sub("open", "closed").to_s)
|
57
72
|
end
|
data/spec/fwd/cli_spec.rb
CHANGED
@@ -9,6 +9,8 @@ describe Fwd::CLI do
|
|
9
9
|
"--bind", "tcp://127.0.0.1:7289",
|
10
10
|
"--forward", "tcp://1.2.3.4:1234,tcp://1.2.3.5:1235",
|
11
11
|
"--flush", "1200:90",
|
12
|
+
"--log", "/dev/null",
|
13
|
+
"--verbose",
|
12
14
|
]
|
13
15
|
end
|
14
16
|
|
@@ -19,6 +21,8 @@ describe Fwd::CLI do
|
|
19
21
|
its([:forward]) { should == ["tcp://1.2.3.4:1234", "tcp://1.2.3.5:1235"] }
|
20
22
|
its([:flush_rate]) { should == 1200 }
|
21
23
|
its([:flush_interval]) { should == 90 }
|
24
|
+
its([:log]) { should == "/dev/null" }
|
25
|
+
its([:log_level]) { should == 0 }
|
22
26
|
its(:core) { should be_instance_of(Fwd) }
|
23
27
|
|
24
28
|
end
|
data/spec/fwd/input_spec.rb
CHANGED
data/spec/fwd/output_spec.rb
CHANGED
@@ -2,6 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Fwd::Output do
|
4
4
|
|
5
|
+
before { FileUtils.mkdir_p root.to_s }
|
5
6
|
let(:output) { described_class.new core }
|
6
7
|
subject { output }
|
7
8
|
|
@@ -47,35 +48,37 @@ describe Fwd::Output do
|
|
47
48
|
subject.pool.checkout {|c| c.should be_instance_of(Fwd::Backend) }
|
48
49
|
end
|
49
50
|
|
50
|
-
describe "
|
51
|
-
|
52
|
-
|
51
|
+
describe "streaming data" do
|
52
|
+
|
53
|
+
def stream(data)
|
54
|
+
tmp = root.join("temp.file")
|
55
|
+
tmp.open("wb") {|f| f << data }
|
56
|
+
subject.stream_file(tmp)
|
53
57
|
end
|
54
58
|
|
55
59
|
it 'should forward data to backends' do
|
56
60
|
servers(7291, 7292) do
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
+
stream("A").should be(true)
|
62
|
+
stream("B").should be(true)
|
63
|
+
stream("C").should be(true)
|
64
|
+
stream("D").should be(true)
|
61
65
|
end.should == { 7291=>"BD", 7292=>"AC" }
|
62
66
|
end
|
63
67
|
|
64
68
|
it 'should handle partial fallouts' do
|
65
69
|
servers(7291) do
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
sleep(1)
|
70
|
+
stream("A").should be(true)
|
71
|
+
stream("B").should be(true)
|
72
|
+
stream("C").should be(true)
|
73
|
+
stream("D").should be(true)
|
71
74
|
end.should == { 7291=>"ABCD" }
|
72
75
|
end
|
73
76
|
|
74
77
|
it 'should handle full fallouts' do
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
78
|
+
stream("A").should be(false)
|
79
|
+
stream("B").should be(false)
|
80
|
+
stream("C").should be(false)
|
81
|
+
stream("D").should be(false)
|
79
82
|
end
|
80
83
|
|
81
84
|
end
|
@@ -83,7 +86,7 @@ describe Fwd::Output do
|
|
83
86
|
describe "forwarding" do
|
84
87
|
|
85
88
|
def write(file)
|
86
|
-
file.open("
|
89
|
+
file.open("wb") {|f| f << "x" }
|
87
90
|
file
|
88
91
|
end
|
89
92
|
|
@@ -91,14 +94,18 @@ describe Fwd::Output do
|
|
91
94
|
Dir[root.join(glob)].map {|f| File.basename f }.sort
|
92
95
|
end
|
93
96
|
|
94
|
-
before { subject.stub!
|
95
|
-
before { FileUtils.mkdir_p root.to_s }
|
97
|
+
before { subject.stub! stream_file: true }
|
96
98
|
let!(:f1) { write root.join("buffer.1.closed") }
|
97
99
|
let!(:f2) { write root.join("buffer.2.open") }
|
98
100
|
let!(:f3) { write root.join("buffer.3.closed") }
|
99
101
|
|
100
|
-
it 'should
|
101
|
-
subject.should_receive(:
|
102
|
+
it 'should send the data' do
|
103
|
+
subject.should_receive(:stream_file).twice.and_return(true)
|
104
|
+
subject.forward!
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should stop on first failure' do
|
108
|
+
subject.should_receive(:stream_file).once.and_return(false)
|
102
109
|
subject.forward!
|
103
110
|
end
|
104
111
|
|
@@ -108,9 +115,9 @@ describe Fwd::Output do
|
|
108
115
|
}.to(["buffer.2.open"])
|
109
116
|
end
|
110
117
|
|
111
|
-
it 'should handle
|
112
|
-
subject.should_receive(:
|
113
|
-
subject.should_receive(:
|
118
|
+
it 'should handle revert failed files' do
|
119
|
+
subject.should_receive(:stream_file).and_return(true)
|
120
|
+
subject.should_receive(:stream_file).and_return(false)
|
114
121
|
|
115
122
|
lambda { subject.forward! }.should change {
|
116
123
|
files
|
data/spec/fwd_spec.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -21,6 +21,8 @@ module Fwd::TestHelper
|
|
21
21
|
def core
|
22
22
|
@_core ||= Fwd.new \
|
23
23
|
path: root,
|
24
|
+
log: "/dev/null",
|
25
|
+
log_level: Logger::DEBUG,
|
24
26
|
flush_rate: 20,
|
25
27
|
buffer_limit: 2048,
|
26
28
|
forward: ["tcp://127.0.0.1:7291", "tcp://127.0.0.1:7292"]
|
@@ -34,9 +36,6 @@ end
|
|
34
36
|
|
35
37
|
RSpec.configure do |c|
|
36
38
|
c.include(Fwd::TestHelper)
|
37
|
-
c.before(:suite) do
|
38
|
-
Fwd.logger = Logger.new("/dev/null")
|
39
|
-
end
|
40
39
|
c.before(:each) do
|
41
40
|
FileUtils.rm_rf root.to_s
|
42
41
|
EM.stub add_periodic_timer: timer
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fwd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.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: 2013-
|
12
|
+
date: 2013-03-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: eventmachine-le
|
@@ -131,6 +131,7 @@ extensions: []
|
|
131
131
|
extra_rdoc_files: []
|
132
132
|
files:
|
133
133
|
- .gitignore
|
134
|
+
- .travis.yml
|
134
135
|
- Gemfile
|
135
136
|
- Gemfile.lock
|
136
137
|
- Rakefile
|