fwd 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fwd (0.3.2)
4
+ fwd (0.3.3)
5
5
  connection_pool
6
6
  eventmachine-le
7
7
  servolux
@@ -6,29 +6,31 @@ require 'socket'
6
6
  require 'fileutils'
7
7
 
8
8
  root = Pathname.new(File.expand_path('../..', __FILE__))
9
- tmp = root.join("tmp/benchmark")
10
- FileUtils.rm_rf tmp
11
- FileUtils.mkdir_p tmp
9
+ TMP = root.join("tmp/benchmark")
10
+ OUT = TMP.join('out.txt')
12
11
 
13
- OUT = tmp.join('out.txt')
14
- FWD = fork { exec "#{root}/bin/fwd-rb --flush 10000:2 -F tcp://0.0.0.0:7291 --path #{tmp} -v" }
15
- NCC = fork { exec "nc -vlp 7291 > #{OUT}" }
12
+ FileUtils.rm_rf TMP
13
+ FileUtils.mkdir_p TMP
14
+ FileUtils.touch(OUT)
16
15
 
17
- at_exit do
18
- Process.kill(:TERM, FWD)
19
- Process.kill(:TERM, NCC)
20
- end
16
+ FWD = fork { exec "#{root}/bin/fwd-rb --flush 10000:2 -F tcp://0.0.0.0:7291 --path #{TMP} -v" }
17
+ NCC = fork { exec "nc -vlp 7291 > #{OUT}" }
21
18
 
22
- sleep(3)
19
+ sleep(5)
23
20
 
24
21
  EVENTS = 10_000_000
25
22
  LENGTH = 100
26
23
  DATA = "A" * LENGTH
27
-
28
- ds = Benchmark.realtime do
29
- sock = TCPSocket.new "127.0.0.1", 7289
30
- EVENTS.times { sock.write DATA }
31
- sock.close
24
+ CCUR = 5
25
+
26
+ ds = Benchmark.realtime do
27
+ (1..CCUR).map do
28
+ fork do
29
+ sock = TCPSocket.new "127.0.0.1", 7289
30
+ (EVENTS / CCUR).times { sock.write DATA }
31
+ sock.close
32
+ end
33
+ end.each {|t| Process.wait(t) }
32
34
  end
33
35
 
34
36
  rs = Benchmark.realtime do
@@ -44,3 +46,6 @@ puts "--> Completed in : #{(ds + rs).round(1)}s"
44
46
  puts "--> FWD RSS : #{(`ps -o rss= -p #{FWD}`.to_f / 1024).round(1)}M"
45
47
  puts "--> Processed : #{EVENTS} events"
46
48
  puts "--> Written : #{(OUT.size / 1024.0 / 1024.0).round(1)}M"
49
+
50
+ Process.kill(:TERM, FWD)
51
+ Process.kill(:TERM, NCC)
@@ -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.3.2"
12
+ s.version = "0.3.3"
13
13
 
14
14
  s.authors = ["Black Square Media"]
15
15
  s.email = "info@blacksquaremedia.com"
data/lib/fwd.rb CHANGED
@@ -87,7 +87,8 @@ class Fwd
87
87
  # Starts the server
88
88
  def listen!
89
89
  logger.info "Starting server on #{@bind}"
90
- EM.start_server @bind.host, @bind.port, Fwd::Input, self
90
+ buffer = Fwd::Buffer.new(self)
91
+ EM.start_server @bind.host, @bind.port, Fwd::Input, self, buffer
91
92
  end
92
93
 
93
94
  # Initiates flush
@@ -13,9 +13,7 @@ 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
-
17
16
  reschedule!
18
- rotate!
19
17
  end
20
18
 
21
19
  # @param [String] data binary data
@@ -50,7 +48,8 @@ class Fwd::Buffer
50
48
  end
51
49
 
52
50
  @fd = new_file
53
- rescue Errno::ENOENT
51
+ rescue Errno::ENOENT => e
52
+ logger.warn "Rotation delayed: #{e.message}"
54
53
  end
55
54
 
56
55
  # @return [Boolean] true if rotation is due
@@ -5,13 +5,10 @@ class Fwd::Input < EM::Connection
5
5
  attr_reader :core, :buffer
6
6
 
7
7
  # @param [Fwd] core
8
- # @param [Hash] opts additional opts
9
- def initialize(core)
10
- @core = core
11
- end
12
-
13
- def post_init
14
- @buffer = Fwd::Buffer.new(core)
8
+ # @param [Fwd::Buffer] buffer
9
+ def initialize(core, buffer)
10
+ @core = core
11
+ @buffer = buffer
15
12
  end
16
13
 
17
14
  # When receiving data, concat it to the buffer
@@ -2,7 +2,8 @@ class Fwd::Output
2
2
  extend Forwardable
3
3
  def_delegators :core, :logger, :root, :prefix
4
4
 
5
- RESCUABLE = [
5
+ CHUNK_SIZE = 16 * 1024
6
+ RESCUABLE = [
6
7
  Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EHOSTUNREACH, Errno::EPIPE,
7
8
  Errno::ENETUNREACH, Errno::ENETDOWN, Errno::EINVAL, Errno::ETIMEDOUT,
8
9
  IOError, EOFError
@@ -22,19 +23,26 @@ class Fwd::Output
22
23
 
23
24
  # Callback
24
25
  def forward!
25
- Dir[root.join("#{prefix}.*.closed")].each do |file|
26
- ok = reserve(file) do |data|
27
- logger.debug { "Flushing #{File.basename(file)}, #{data.size.fdiv(1024).round} kB" }
28
- write(data)
26
+ return if @forwarding
27
+
28
+ @forwarding = true
29
+ begin
30
+ Dir[root.join("#{prefix}.*.closed")].sort.each do |file|
31
+ ok = reserve(file) do |io|
32
+ logger.debug { "Flushing #{File.basename(io.path)}, #{io.size.fdiv(1024).round} kB" }
33
+ write(io)
34
+ end
35
+ ok or break
29
36
  end
30
- break unless ok
37
+ ensure
38
+ @forwarding = false
31
39
  end
32
40
  end
33
41
 
34
- # @param [String] binary data
35
- def write(data)
42
+ # @param [IO] io source stream
43
+ def write(io)
36
44
  pool.any? do |backend|
37
- forward(backend, data)
45
+ forward(backend, io)
38
46
  end
39
47
  end
40
48
 
@@ -46,7 +54,11 @@ class Fwd::Output
46
54
  target = Pathname.new(file.sub(/\.closed$/, ".reserved"))
47
55
  FileUtils.mv file, target.to_s
48
56
 
49
- result = yield(target.read)
57
+ result = false
58
+ target.open("r") do |io|
59
+ result = yield(io)
60
+ end
61
+
50
62
  if result
51
63
  target.unlink
52
64
  else
@@ -55,12 +67,17 @@ class Fwd::Output
55
67
  end
56
68
 
57
69
  result
58
- rescue Errno::ENOENT
70
+ rescue Errno::ENOENT => e
59
71
  # Ignore if file was alread flushed by another process
72
+ logger.warn "Flushing of #{File.basename(file)} postponed: #{e.message}"
60
73
  end
61
74
 
62
- def forward(backend, data)
63
- backend.write(data) && true
75
+ def forward(backend, io)
76
+ io.rewind
77
+ until io.eof?
78
+ backend.write(io.read(CHUNK_SIZE))
79
+ end
80
+ true
64
81
  rescue *RESCUABLE => e
65
82
  logger.error "Backend #{backend} failed: #{e.class.name} #{e.message}"
66
83
  backend.close
@@ -7,14 +7,9 @@ describe Fwd::Buffer do
7
7
  end
8
8
 
9
9
  let(:buffer) { described_class.new core }
10
- let(:timer) { mock("Timer", cancel: true) }
11
10
  subject { buffer }
12
- before do
13
- EM.stub add_periodic_timer: timer
14
- end
15
11
 
16
12
  its(:root) { should == root }
17
- its(:root) { should be_exist }
18
13
  its(:prefix) { should == "buffer" }
19
14
  its(:core) { should be(core) }
20
15
  its(:count) { should be(0) }
@@ -22,21 +17,26 @@ describe Fwd::Buffer do
22
17
  its(:rate) { should be(20) }
23
18
  its(:limit) { should be(2048) }
24
19
  its(:timer) { should be(timer) }
25
- its(:fd) { should be_instance_of(File) }
20
+ its(:fd) { should be_nil }
26
21
  its(:logger) { should be(Fwd.logger) }
27
22
 
28
23
  describe "concat" do
29
24
  it 'should concat data' do
30
- lambda {
31
- subject.concat("x" * 1024)
32
- }.should change {
33
- subject.fd.size
34
- }.by(1024)
25
+ subject.concat("x" * 1024)
26
+ subject.fd.size.should == 1024
27
+ end
28
+
29
+ it 'should create the path' do
30
+ lambda { buffer.concat("x") }.should change { buffer.root.exist? }.to(true)
31
+ end
32
+
33
+ it 'should rotate' do
34
+ lambda { buffer.concat("x") }.should change { buffer.fd }.to(instance_of(File))
35
35
  end
36
36
  end
37
37
 
38
38
  describe "rotate" do
39
- before { buffer }
39
+ before { buffer.send(:rotate!) }
40
40
  subject { lambda { buffer.rotate! } }
41
41
 
42
42
  it 'should trigger when buffer limit is reached' do
@@ -58,7 +58,7 @@ describe Fwd::Buffer do
58
58
  end
59
59
 
60
60
  describe "when unchanged" do
61
- it { should_not change { buffer.fd.path } }
61
+ it { should_not change { buffer.fd.size }.from(0) }
62
62
  it { should_not change { files.size } }
63
63
  end
64
64
  end
@@ -2,20 +2,18 @@ require 'spec_helper'
2
2
 
3
3
  describe Fwd::Input do
4
4
 
5
+ let(:buffer) { Fwd::Buffer.new(core) }
5
6
  subject do
6
7
  input = described_class.allocate
7
- input.send(:initialize, core)
8
+ input.send(:initialize, core, buffer)
8
9
  input
9
10
  end
11
+
10
12
  before { EM.stub :add_periodic_timer }
11
13
 
12
14
  it { should be_a(EM::Connection) }
13
- its(:buffer) { should be_nil }
15
+ its(:core) { should be(core) }
16
+ its(:buffer) { should be(buffer) }
14
17
  its(:logger) { should be(Fwd.logger) }
15
18
 
16
- describe "post init" do
17
- before { subject.post_init }
18
- its(:buffer) { should be_instance_of(Fwd::Buffer) }
19
- end
20
-
21
19
  end
@@ -48,30 +48,34 @@ describe Fwd::Output do
48
48
  end
49
49
 
50
50
  describe "writing" do
51
+ def write(data)
52
+ subject.write(StringIO.new(data))
53
+ end
51
54
 
52
55
  it 'should forward data to backends' do
53
56
  servers(7291, 7292) do
54
- subject.write("A").should be(true)
55
- subject.write("B").should be(true)
56
- subject.write("C").should be(true)
57
- subject.write("D").should be(true)
57
+ write("A").should be(true)
58
+ write("B").should be(true)
59
+ write("C").should be(true)
60
+ write("D").should be(true)
58
61
  end.should == { 7291=>"BD", 7292=>"AC" }
59
62
  end
60
63
 
61
64
  it 'should handle partial fallouts' do
62
65
  servers(7291) do
63
- subject.write("A").should be(true)
64
- subject.write("B").should be(true)
65
- subject.write("C").should be(true)
66
- subject.write("D").should be(true)
66
+ write("A").should be(true)
67
+ write("B").should be(true)
68
+ write("C").should be(true)
69
+ write("D").should be(true)
70
+ sleep(1)
67
71
  end.should == { 7291=>"ABCD" }
68
72
  end
69
73
 
70
74
  it 'should handle full fallouts' do
71
- subject.write("A").should be(false)
72
- subject.write("B").should be(false)
73
- subject.write("C").should be(false)
74
- subject.write("D").should be(false)
75
+ write("A").should be(false)
76
+ write("B").should be(false)
77
+ write("C").should be(false)
78
+ write("D").should be(false)
75
79
  end
76
80
 
77
81
  end
@@ -26,6 +26,10 @@ module Fwd::TestHelper
26
26
  forward: ["tcp://127.0.0.1:7291", "tcp://127.0.0.1:7292"]
27
27
  end
28
28
 
29
+ def timer
30
+ @_timer ||= mock("Timer", cancel: true)
31
+ end
32
+
29
33
  end
30
34
 
31
35
  RSpec.configure do |c|
@@ -35,5 +39,6 @@ RSpec.configure do |c|
35
39
  end
36
40
  c.before(:each) do
37
41
  FileUtils.rm_rf root.to_s
42
+ EM.stub add_periodic_timer: timer
38
43
  end
39
44
  end
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.3.2
4
+ version: 0.3.3
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-02-20 00:00:00.000000000 Z
12
+ date: 2013-02-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine-le