dtas 0.0.0
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.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/.gitignore +9 -0
- data/.rsync_doc +3 -0
- data/COPYING +674 -0
- data/Documentation/.gitignore +3 -0
- data/Documentation/GNUmakefile +46 -0
- data/Documentation/dtas-console.txt +42 -0
- data/Documentation/dtas-ctl.txt +64 -0
- data/Documentation/dtas-cueedit.txt +24 -0
- data/Documentation/dtas-enq.txt +29 -0
- data/Documentation/dtas-msinkctl.txt +45 -0
- data/Documentation/dtas-player.txt +110 -0
- data/Documentation/dtas-player_effects.txt +45 -0
- data/Documentation/dtas-player_protocol.txt +181 -0
- data/Documentation/dtas-sinkedit.txt +41 -0
- data/Documentation/dtas-sourceedit.txt +33 -0
- data/Documentation/dtas-xdelay.txt +57 -0
- data/Documentation/troubleshooting.txt +13 -0
- data/GIT-VERSION-GEN +30 -0
- data/GNUmakefile +9 -0
- data/HACKING +12 -0
- data/INSTALL +53 -0
- data/README +103 -0
- data/Rakefile +97 -0
- data/TODO +4 -0
- data/bin/dtas-console +160 -0
- data/bin/dtas-ctl +10 -0
- data/bin/dtas-cueedit +78 -0
- data/bin/dtas-enq +13 -0
- data/bin/dtas-msinkctl +51 -0
- data/bin/dtas-player +34 -0
- data/bin/dtas-sinkedit +58 -0
- data/bin/dtas-sourceedit +48 -0
- data/bin/dtas-xdelay +85 -0
- data/dtas-linux.gemspec +18 -0
- data/dtas-mpris.gemspec +16 -0
- data/examples/dtas_state.yml +18 -0
- data/lib/dtas.rb +7 -0
- data/lib/dtas/buffer.rb +90 -0
- data/lib/dtas/buffer/read_write.rb +102 -0
- data/lib/dtas/buffer/splice.rb +142 -0
- data/lib/dtas/command.rb +43 -0
- data/lib/dtas/compat_onenine.rb +18 -0
- data/lib/dtas/disclaimer.rb +18 -0
- data/lib/dtas/format.rb +151 -0
- data/lib/dtas/pipe.rb +39 -0
- data/lib/dtas/player.rb +393 -0
- data/lib/dtas/player/client_handler.rb +463 -0
- data/lib/dtas/process.rb +87 -0
- data/lib/dtas/replaygain.rb +41 -0
- data/lib/dtas/rg_state.rb +99 -0
- data/lib/dtas/serialize.rb +9 -0
- data/lib/dtas/sigevent.rb +10 -0
- data/lib/dtas/sigevent/efd.rb +20 -0
- data/lib/dtas/sigevent/pipe.rb +28 -0
- data/lib/dtas/sink.rb +121 -0
- data/lib/dtas/source.rb +147 -0
- data/lib/dtas/source/command.rb +40 -0
- data/lib/dtas/source/common.rb +14 -0
- data/lib/dtas/source/mp3.rb +37 -0
- data/lib/dtas/state_file.rb +33 -0
- data/lib/dtas/unix_accepted.rb +76 -0
- data/lib/dtas/unix_client.rb +51 -0
- data/lib/dtas/unix_server.rb +110 -0
- data/lib/dtas/util.rb +15 -0
- data/lib/dtas/writable_iter.rb +22 -0
- data/perl/dtas-graph +129 -0
- data/pkg.mk +26 -0
- data/setup.rb +1586 -0
- data/test/covshow.rb +30 -0
- data/test/helper.rb +76 -0
- data/test/player_integration.rb +121 -0
- data/test/test_buffer.rb +216 -0
- data/test/test_format.rb +61 -0
- data/test/test_format_change.rb +49 -0
- data/test/test_player.rb +47 -0
- data/test/test_player_client_handler.rb +86 -0
- data/test/test_player_integration.rb +220 -0
- data/test/test_rg_integration.rb +117 -0
- data/test/test_rg_state.rb +32 -0
- data/test/test_sink.rb +32 -0
- data/test/test_sink_tee_integration.rb +34 -0
- data/test/test_source.rb +102 -0
- data/test/test_unixserver.rb +66 -0
- data/test/test_util.rb +15 -0
- metadata +208 -0
data/bin/dtas-xdelay
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding: binary -*-
|
3
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
4
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
5
|
+
USAGE = "Usage: #$0 [-x FREQ] [-l] /dev/fd/LO /dev/fd/HI DELAY [DELAY ...]"
|
6
|
+
require 'optparse'
|
7
|
+
dryrun = false
|
8
|
+
xover = '80'
|
9
|
+
delay_lo = []
|
10
|
+
delay_hi = []
|
11
|
+
adj_delay = delay_hi
|
12
|
+
out_channels = out_rate = out_type = nil
|
13
|
+
|
14
|
+
lowpass = 'lowpass %s lowpass %s'
|
15
|
+
highpass = 'highpass %s highpass %s'
|
16
|
+
|
17
|
+
op = OptionParser.new('', 24, ' ') do |opts|
|
18
|
+
opts.banner = USAGE
|
19
|
+
opts.on('-x', '--crossover-frequency FREQ') do |freq|
|
20
|
+
xover = freq
|
21
|
+
end
|
22
|
+
opts.on('-l', '--lowpass-delay') { adj_delay = delay_lo }
|
23
|
+
opts.on('-c', '--channels INTEGER') { |val| out_channels = val }
|
24
|
+
opts.on('-r', '--rate RATE') { |val| out_rate = val }
|
25
|
+
opts.on('-t', '--type FILE-TYPE') { |val| out_type = val }
|
26
|
+
opts.on('-n', '--dry-run') { dryrun = true }
|
27
|
+
opts.on('--lowpass FORMAT_STRING') { |s| lowpass = s }
|
28
|
+
opts.on('--highpass FORMAT_STRING') { |s| highpass = s }
|
29
|
+
opts.parse!(ARGV)
|
30
|
+
end
|
31
|
+
|
32
|
+
dev_fd_lo = ARGV.shift
|
33
|
+
dev_fd_hi = ARGV.shift
|
34
|
+
if ARGV.delete('-')
|
35
|
+
# we re-add the '-' below
|
36
|
+
out_channels && out_rate && out_type or
|
37
|
+
abort "-c, -r, and -t must all be specified for standard output"
|
38
|
+
cmd = "sox"
|
39
|
+
elsif out_channels || out_rate || out_type
|
40
|
+
abort "standard output (`-') must be specified with -c, -r, or -t"
|
41
|
+
else
|
42
|
+
cmd = "play"
|
43
|
+
end
|
44
|
+
soxfmt = ENV["SOXFMT"] or abort "#$0 SOXFMT undefined"
|
45
|
+
|
46
|
+
# configure the sox "delay" effect
|
47
|
+
delay = ARGV.dup
|
48
|
+
delay[0] or abort USAGE
|
49
|
+
channels = ENV['CHANNELS'] or abort "#$0 CHANNELS env must be set"
|
50
|
+
channels = channels.to_i
|
51
|
+
adj_delay.replace(delay.dup)
|
52
|
+
until adj_delay.size == channels
|
53
|
+
adj_delay << delay.last
|
54
|
+
end
|
55
|
+
adj_delay.unshift("delay")
|
56
|
+
|
57
|
+
# prepare two inputs:
|
58
|
+
delay_lo = delay_lo.join(' ')
|
59
|
+
delay_hi = delay_hi.join(' ')
|
60
|
+
|
61
|
+
lowpass_args = []
|
62
|
+
lowpass.gsub('%s') { |s| lowpass_args << xover; s }
|
63
|
+
highpass_args = []
|
64
|
+
highpass.gsub('%s') { |s| highpass_args << xover; s }
|
65
|
+
|
66
|
+
lo = "|exec sox #{soxfmt} #{dev_fd_lo} -p " \
|
67
|
+
"#{sprintf(lowpass, *lowpass_args)} #{delay_lo}".strip
|
68
|
+
hi = "|exec sox #{soxfmt} #{dev_fd_hi} -p " \
|
69
|
+
"#{sprintf(highpass, *highpass_args)} #{delay_hi}".strip
|
70
|
+
|
71
|
+
args = [ "-m", "-v1", lo, "-v1", hi ]
|
72
|
+
case cmd
|
73
|
+
when "sox"
|
74
|
+
args.unshift "sox"
|
75
|
+
args.concat(%W(-t#{out_type} -c#{out_channels} -r#{out_rate} -))
|
76
|
+
when "play"
|
77
|
+
args.unshift "play"
|
78
|
+
else
|
79
|
+
abort "BUG: bad cmd=#{cmd.inspect}"
|
80
|
+
end
|
81
|
+
if dryrun
|
82
|
+
p args
|
83
|
+
else
|
84
|
+
exec *args, close_others: false
|
85
|
+
end
|
data/dtas-linux.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# this just declares dependencies to make gem installation a little easier
|
3
|
+
# for Linux users
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = %q{dtas-linux}
|
6
|
+
s.version = '1.0.0'
|
7
|
+
s.authors = ["dtas hackers"]
|
8
|
+
s.summary = "meta-package for dtas users on the Linux kernel"
|
9
|
+
s.description = "gives small performance improvements for dtas users\n" \
|
10
|
+
"via tee(), splice() and eventfd() on Linux"
|
11
|
+
s.email = %q{e@80x24.org}
|
12
|
+
s.files = []
|
13
|
+
s.homepage = 'http://dtas.80x24.org/'
|
14
|
+
s.add_dependency(%q<dtas>)
|
15
|
+
s.add_dependency(%q<io_splice>, '~> 4')
|
16
|
+
s.add_dependency(%q<sleepy_penguin>, '~> 3')
|
17
|
+
s.licenses = "GPLv3+"
|
18
|
+
end
|
data/dtas-mpris.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = %q{dtas-mpris}
|
4
|
+
s.version = '0.0.0'
|
5
|
+
s.authors = ["dtas hackers"]
|
6
|
+
s.summary = "meta-package for the dtas-mpris proxy"
|
7
|
+
s.description =
|
8
|
+
"this allows controlling dtas-player via MPRIS or MPRIS 2.0\n" \
|
9
|
+
"This is currently a dummy package as dtas-mpris is not implemented"
|
10
|
+
s.email = %q{e@80x24.org}
|
11
|
+
s.files = []
|
12
|
+
s.homepage = 'http://dtas.80x24.org/'
|
13
|
+
s.add_dependency(%q<dtas>)
|
14
|
+
s.add_dependency(%q<ruby-dbus>)
|
15
|
+
s.licenses = "GPLv2+"
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
socket: .dtas/player.sock
|
3
|
+
sinks:
|
4
|
+
- name: ODAC
|
5
|
+
command: |-
|
6
|
+
sox -t $SOX_FILETYPE -r $RATE -c $CHANNELS - \
|
7
|
+
-t s$SINK_BITS -r $SINK_RATE -c $SINK_CHANNELS - | \
|
8
|
+
aplay -D hw:DAC_1 -v -q -M --buffer-size=500000 --period-size=500 \
|
9
|
+
--disable-softvol --start-delay=100 \
|
10
|
+
--disable-format --disable-resample --disable-channels \
|
11
|
+
-t raw -c $SINK_CHANNELS -f S${SINK_BITS}_3LE -r $SINK_RATE
|
12
|
+
env:
|
13
|
+
SINK_BITS: 24
|
14
|
+
SINK_RATE: 44100
|
15
|
+
SINK_CHANNELS: 2
|
16
|
+
- name: play
|
17
|
+
prio: -2
|
18
|
+
command: env AUDIODEV=hw:DAC play -t $SOX_FILETYPE -r $RATE -c $CHANNELS -
|
data/lib/dtas.rb
ADDED
data/lib/dtas/buffer.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require_relative '../dtas'
|
5
|
+
|
6
|
+
class DTAS::Buffer # :nodoc:
|
7
|
+
begin
|
8
|
+
raise LoadError, "no splice with _DTAS_POSIX" if ENV["_DTAS_POSIX"]
|
9
|
+
require 'io/splice' # splice is only in Linux for now...
|
10
|
+
require_relative 'buffer/splice'
|
11
|
+
include DTAS::Buffer::Splice
|
12
|
+
rescue LoadError
|
13
|
+
require_relative 'buffer/read_write'
|
14
|
+
include DTAS::Buffer::ReadWrite
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :to_io # call nread on this
|
18
|
+
attr_reader :wr # processes (sources) should redirect to this
|
19
|
+
attr_accessor :bytes_xfer
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@bytes_xfer = 0
|
23
|
+
@buffer_size = nil
|
24
|
+
@to_io, @wr = DTAS::Pipe.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.load(hash)
|
28
|
+
buf = new
|
29
|
+
if hash
|
30
|
+
bs = hash["buffer_size"] and buf.buffer_size = bs
|
31
|
+
end
|
32
|
+
buf
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_hsh
|
36
|
+
@buffer_size ? { "buffer_size" => @buffer_size } : {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def __dst_error(dst, e)
|
40
|
+
warn "dropping #{dst.inspect} due to error: #{e.message} (#{e.class})"
|
41
|
+
dst.close unless dst.closed?
|
42
|
+
end
|
43
|
+
|
44
|
+
# This will modify targets
|
45
|
+
# returns one of:
|
46
|
+
# - :wait_readable
|
47
|
+
# - subset of targets array for :wait_writable
|
48
|
+
# - some type of StandardError
|
49
|
+
# - nil
|
50
|
+
def broadcast(targets)
|
51
|
+
bytes = inflight
|
52
|
+
return :wait_readable if 0 == bytes # spurious wakeup
|
53
|
+
|
54
|
+
case targets.size
|
55
|
+
when 0
|
56
|
+
:ignore # this will pause decoders
|
57
|
+
when 1
|
58
|
+
broadcast_one(targets, bytes)
|
59
|
+
else # infinity
|
60
|
+
broadcast_inf(targets, bytes)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def readable_iter
|
65
|
+
# this calls DTAS::Buffer#broadcast from DTAS::Player
|
66
|
+
yield(self, nil)
|
67
|
+
end
|
68
|
+
|
69
|
+
def inflight
|
70
|
+
@to_io.nread
|
71
|
+
end
|
72
|
+
|
73
|
+
# don't really close the pipes under normal circumstances, just clear data
|
74
|
+
def close
|
75
|
+
bytes = inflight
|
76
|
+
discard(bytes) if bytes > 0
|
77
|
+
end
|
78
|
+
|
79
|
+
def buf_reset
|
80
|
+
close!
|
81
|
+
@bytes_xfer = 0
|
82
|
+
@to_io, @wr = DTAS::Pipe.new
|
83
|
+
@wr.pipe_size = @buffer_size if @buffer_size
|
84
|
+
end
|
85
|
+
|
86
|
+
def close!
|
87
|
+
@to_io.close
|
88
|
+
@wr.close
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require 'io/wait'
|
5
|
+
require 'io/nonblock'
|
6
|
+
require_relative '../../dtas'
|
7
|
+
require_relative '../pipe'
|
8
|
+
|
9
|
+
module DTAS::Buffer::ReadWrite # :nodoc:
|
10
|
+
MAX_AT_ONCE = 512 # min PIPE_BUF value in POSIX
|
11
|
+
attr_accessor :buffer_size
|
12
|
+
|
13
|
+
def _rbuf
|
14
|
+
Thread.current[:dtas_pbuf] ||= ""
|
15
|
+
end
|
16
|
+
|
17
|
+
# be sure to only call this with nil when all writers to @wr are done
|
18
|
+
def discard(bytes)
|
19
|
+
buf = _rbuf
|
20
|
+
begin
|
21
|
+
@to_io.read(bytes, buf) or break # EOF
|
22
|
+
bytes -= buf.bytesize
|
23
|
+
end until bytes == 0
|
24
|
+
end
|
25
|
+
|
26
|
+
# always block when we have a single target
|
27
|
+
def broadcast_one(targets, bytes)
|
28
|
+
buf = _rbuf
|
29
|
+
@to_io.read(bytes, buf)
|
30
|
+
n = targets[0].write(buf) # IO#write has write-in-full behavior
|
31
|
+
@bytes_xfer += n
|
32
|
+
:wait_readable
|
33
|
+
rescue Errno::EPIPE, IOError => e
|
34
|
+
__dst_error(targets[0], e)
|
35
|
+
targets.clear
|
36
|
+
nil # do not return error here, we already spewed an error message
|
37
|
+
end
|
38
|
+
|
39
|
+
def broadcast_inf(targets, bytes)
|
40
|
+
nr_nb = targets.count { |sink| sink.nonblock? }
|
41
|
+
if nr_nb == 0 || nr_nb == targets.size
|
42
|
+
# if all targets are full, don't start until they're all writable
|
43
|
+
r = IO.select(nil, targets, nil, 0) or return targets
|
44
|
+
blocked = targets - r[1]
|
45
|
+
|
46
|
+
# tell DTAS::UNIXServer#run_once to wait on the blocked targets
|
47
|
+
return blocked if blocked[0]
|
48
|
+
|
49
|
+
# all writable, yay!
|
50
|
+
else
|
51
|
+
blocked = []
|
52
|
+
end
|
53
|
+
|
54
|
+
again = {}
|
55
|
+
|
56
|
+
# don't pin too much on one target
|
57
|
+
bytes = bytes > MAX_AT_ONCE ? MAX_AT_ONCE : bytes
|
58
|
+
buf = _rbuf
|
59
|
+
@to_io.read(bytes, buf)
|
60
|
+
@bytes_xfer += buf.bytesize
|
61
|
+
|
62
|
+
targets.delete_if do |dst|
|
63
|
+
begin
|
64
|
+
if dst.nonblock?
|
65
|
+
w = dst.write_nonblock(buf)
|
66
|
+
again[dst] = buf.byteslice(w, n) if w < n
|
67
|
+
else
|
68
|
+
dst.write(buf)
|
69
|
+
end
|
70
|
+
false
|
71
|
+
rescue Errno::EAGAIN
|
72
|
+
blocked << dst
|
73
|
+
false
|
74
|
+
rescue IOError, Errno::EPIPE => e
|
75
|
+
again.delete(dst)
|
76
|
+
__dst_error(dst, e)
|
77
|
+
true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# try to write as much as possible
|
82
|
+
again.delete_if do |dst, sbuf|
|
83
|
+
begin
|
84
|
+
w = dst.write_nonblock(sbuf)
|
85
|
+
n = sbuf.bytesize
|
86
|
+
if w < n
|
87
|
+
again[dst] = sbuf.byteslice(w, n)
|
88
|
+
false
|
89
|
+
else
|
90
|
+
true
|
91
|
+
end
|
92
|
+
rescue Errno::EAGAIN
|
93
|
+
blocked << dst
|
94
|
+
true
|
95
|
+
rescue IOError, Errno::EPIPE => e
|
96
|
+
__dst_error(dst, e)
|
97
|
+
true
|
98
|
+
end
|
99
|
+
end until again.empty?
|
100
|
+
targets[0] ? :wait_readable : nil
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require 'io/wait'
|
5
|
+
require 'io/nonblock'
|
6
|
+
require 'io/splice'
|
7
|
+
require_relative '../../dtas'
|
8
|
+
require_relative '../pipe'
|
9
|
+
|
10
|
+
module DTAS::Buffer::Splice # :nodoc:
|
11
|
+
MAX_AT_ONCE = 4096 # page size in Linux
|
12
|
+
MAX_SIZE = File.read("/proc/sys/fs/pipe-max-size").to_i
|
13
|
+
DEVNULL = File.open("/dev/null", "r+")
|
14
|
+
F_MOVE = IO::Splice::F_MOVE
|
15
|
+
WAITALL = IO::Splice::WAITALL
|
16
|
+
|
17
|
+
def buffer_size
|
18
|
+
@to_io.pipe_size
|
19
|
+
end
|
20
|
+
|
21
|
+
# nil is OK, won't reset existing pipe, either...
|
22
|
+
def buffer_size=(bytes)
|
23
|
+
@to_io.pipe_size = bytes if bytes
|
24
|
+
@buffer_size = bytes
|
25
|
+
end
|
26
|
+
|
27
|
+
# be sure to only call this with nil when all writers to @wr are done
|
28
|
+
def discard(bytes)
|
29
|
+
IO.splice(@to_io, nil, DEVNULL, nil, bytes)
|
30
|
+
end
|
31
|
+
|
32
|
+
def broadcast_one(targets, bytes)
|
33
|
+
# single output is always non-blocking
|
34
|
+
s = IO.trysplice(@to_io, nil, targets[0], nil, bytes, F_MOVE)
|
35
|
+
if Symbol === s
|
36
|
+
targets # our one and only target blocked on write
|
37
|
+
else
|
38
|
+
@bytes_xfer += s
|
39
|
+
:wait_readable # we want to read more from @to_io soon
|
40
|
+
end
|
41
|
+
rescue Errno::EPIPE, IOError => e
|
42
|
+
__dst_error(targets[0], e)
|
43
|
+
targets.clear
|
44
|
+
nil # do not return error here, we already spewed an error message
|
45
|
+
end
|
46
|
+
|
47
|
+
# returns the largest value we teed
|
48
|
+
def __broadcast_tee(blocked, targets, chunk_size)
|
49
|
+
most_teed = 0
|
50
|
+
targets.delete_if do |dst|
|
51
|
+
begin
|
52
|
+
t = dst.nonblock? ?
|
53
|
+
IO.trytee(@to_io, dst, chunk_size) :
|
54
|
+
IO.tee(@to_io, dst, chunk_size, WAITALL)
|
55
|
+
if Integer === t
|
56
|
+
most_teed = t if t > most_teed
|
57
|
+
else
|
58
|
+
blocked << dst
|
59
|
+
end
|
60
|
+
false
|
61
|
+
rescue IOError, Errno::EPIPE => e
|
62
|
+
__dst_error(dst, e)
|
63
|
+
true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
most_teed
|
67
|
+
end
|
68
|
+
|
69
|
+
def broadcast_inf(targets, bytes)
|
70
|
+
if targets.none? { |sink| sink.nonblock? }
|
71
|
+
# if all targets are blocking, don't start until they're all writable
|
72
|
+
r = IO.select(nil, targets, nil, 0) or return targets
|
73
|
+
blocked = targets - r[1]
|
74
|
+
|
75
|
+
# tell DTAS::UNIXServer#run_once to wait on the blocked targets
|
76
|
+
return blocked if blocked[0]
|
77
|
+
|
78
|
+
# all writable, yay!
|
79
|
+
else
|
80
|
+
blocked = []
|
81
|
+
end
|
82
|
+
|
83
|
+
# don't pin too much on one target
|
84
|
+
bytes = bytes > MAX_AT_ONCE ? MAX_AT_ONCE : bytes
|
85
|
+
|
86
|
+
last = targets.pop # we splice to the last one, tee to the rest
|
87
|
+
most_teed = __broadcast_tee(blocked, targets, bytes)
|
88
|
+
|
89
|
+
# don't splice more than the largest amount we successfully teed
|
90
|
+
bytes = most_teed if most_teed > 0
|
91
|
+
|
92
|
+
begin
|
93
|
+
targets << last
|
94
|
+
if last.nonblock?
|
95
|
+
s = IO.trysplice(@to_io, nil, last, nil, bytes, F_MOVE)
|
96
|
+
if Symbol === s
|
97
|
+
blocked << last
|
98
|
+
|
99
|
+
# we accomplished nothing!
|
100
|
+
# If _all_ writers are blocked, do not discard data,
|
101
|
+
# stay blocked on :wait_writable
|
102
|
+
return blocked if most_teed == 0
|
103
|
+
|
104
|
+
# the tees targets win, drop data intended for last
|
105
|
+
if most_teed > 0
|
106
|
+
discard(most_teed)
|
107
|
+
@bytes_xfer += most_teed
|
108
|
+
# do not watch for writability of last, last is non-blocking
|
109
|
+
return :wait_readable
|
110
|
+
end
|
111
|
+
end
|
112
|
+
else
|
113
|
+
# the blocking case is simple
|
114
|
+
s = IO.splice(@to_io, nil, last, nil, bytes, WAITALL|F_MOVE)
|
115
|
+
end
|
116
|
+
@bytes_xfer += s
|
117
|
+
|
118
|
+
# if we can't splice everything
|
119
|
+
# discard it so the early targets do not get repeated data
|
120
|
+
if s < bytes && most_teed > 0
|
121
|
+
discard(bytes - s)
|
122
|
+
end
|
123
|
+
:wait_readable
|
124
|
+
rescue IOError, Errno::EPIPE => e # last failed, drop it
|
125
|
+
__dst_error(last, e)
|
126
|
+
targets.pop # we're no longer a valid target
|
127
|
+
|
128
|
+
if most_teed == 0
|
129
|
+
# nothing accomplished, watch any targets
|
130
|
+
return blocked if blocked[0]
|
131
|
+
else
|
132
|
+
# some progress, discard the data we could not splice
|
133
|
+
@bytes_xfer += most_teed
|
134
|
+
discard(most_teed)
|
135
|
+
end
|
136
|
+
|
137
|
+
# stop decoding if we're completely errored out
|
138
|
+
# returning nil will trigger close
|
139
|
+
return targets[0] ? :wait_readable : nil
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|