fwd 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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