Ruby 停止接受管道上的输入,但读取缓冲数据
我有一个应用程序,它读取Ruby 停止接受管道上的输入,但读取缓冲数据,ruby,linux,Ruby,Linux,我有一个应用程序,它读取$stdin,并对数据进行一些处理。我想加入一个信号处理程序来捕获SIGINT/SIGTERM并优雅地关闭(意味着完成数据处理并在完成后退出)。棘手的部分是,我希望它停止从STDIN读取数据,但能够处理任何缓冲数据。这样,就可以启动另一个应用程序并将其传递到相同的STDIN管道,然后在前一个应用程序停止的地方继续处理 问题是,如果我关闭STDIN,缓冲的内容就会丢失,或者至少无法访问 基本上我正在尝试: #!/usr/bin/ruby Signal.trap('INT'
$stdin
,并对数据进行一些处理。我想加入一个信号处理程序来捕获SIGINT/SIGTERM并优雅地关闭(意味着完成数据处理并在完成后退出)。棘手的部分是,我希望它停止从STDIN读取数据,但能够处理任何缓冲数据。这样,就可以启动另一个应用程序并将其传递到相同的STDIN管道,然后在前一个应用程序停止的地方继续处理
问题是,如果我关闭STDIN,缓冲的内容就会丢失,或者至少无法访问
基本上我正在尝试:
#!/usr/bin/ruby
Signal.trap('INT') do
$stdin.close
end
f = File.open('/tmp/out', 'a')
while (data = $stdin.read(4096)) != "" do
f.write(data)
end
它立即在$stdin.read
调用上发出一个IOError
异常,即使我知道它读取了一些数据(strace显示了它)
(我不需要关闭管道,我这样做只是为了打破while
循环。如果有更优雅的方法来打破循环并获取缓冲数据,我很乐意接受。)
我知道这种方法在操作系统级别有效(管道缓冲区在传递到另一个应用程序时会保留),因为我可以执行以下测试,并且不会丢失任何数据:
# source.rb
i = 0
loop do
puts "%08d" % (i += 1)
end
解决此问题的一种方法是在关闭原始文件之前复制文件描述符,然后错误将中断循环,您可以从未关闭的重复文件句柄读取其余数据 (如果此代码不好,很抱歉,我不知道ruby)
在与之斗争了几天之后,我不得不放弃
IO.read
,改用IO.sysread
,自己做缓冲。这个解决方案其实并没有那么复杂,下面是实现
Signal.trap('INT') do
$stdin.close
end
def myread(bufio, bytes) # `bufio` is a StringIO object, `bytes` is bytes to read
begin
while bufio.size < bytes do
bufio.write($stdin.sysread(bytes - bufio.size))
end
rescue SignalException, Interrupt, Errno::EINTR => e
retry
rescue SystemCallError, IOError, EOFError => e
# nothing, we're done
end
end
Signal.trap('INT')do
$stdin.close
结束
def myread(bufio,bytes)#`bufio`是StringIO对象,`bytes`是要读取的字节
开始
而bufio.size<字节
写入($stdin.sysread(字节-bufio.size))
结束
救援信号异常,中断,错误号::EINTR=>e
重试
救援系统调用错误,IOError,EOFError=>e
#没什么,我们完了
结束
结束
我的确切代码与使用AWS ruby SDK时的代码略有不同,因此
myread
方法实际上只是传递给AWS::S3::S3Object的一个块。write
在关闭管道末端和打开新进程之间,您希望发生什么?如果客户端一直将数据推入管道,而没有任何内容在侦听,该怎么办?管道不是这样工作的。如果您需要更具弹性的机制,您可能必须使用消息队列系统来代替。@JimGarrison它应该用来缓冲数据。这正是管道的工作原理(至少在linux中,我不能代表其他操作系统)。编辑:查看您的示例代码,您正在复制$stdout
。但是,当我在$stdin
上尝试时,仍然会出现异常,因为管道关闭时,上一个$stdin.read()
调用仍处于挂起状态。另外,当我复制STDIN并使用dup'd对象时,我仍然可以从传入流中读取数据。我不想从流中读取更多的数据,只需获取缓冲区中的数据即可。@Patrick在这种情况下,(据我所知)没有任何原语可以帮助您完成所需的操作。您给出的将管道连接到多个程序的shell示例只是重复,它实际上没有做任何与此示例不同的事情。我更想听听你关于打破循环的评论,但我可能误解了它。@Patrick如果不尽快读取缓冲数据(非阻塞,因此你知道何时结束),你就无法获取缓冲数据。但问题是它可能永远不会结束,因为管道的重新填充速度可能比清空速度快。此外,修复了标准输入/标准输出错误,但不确定这是如何发生的。
ruby /tmp/source.rb | while true; do ruby reader.rb; sleep 1; done
00000001
00000002
00000003
00000004
00000005
#!/usr/bin/ruby
require 'fcntl'
stdin_dup = nil
Signal.trap('INT') do
stdin_dup = File.for_fd($stdout.fcntl(Fcntl::F_DUPFD))
$stdin.close
end
f = File.open('/tmp/out', 'a')
begin
while (data = $stdin.read(4096)) != "" do
f.write(data)
end
rescue IOError
# finish stuff with stdin_dup here
end
Signal.trap('INT') do
$stdin.close
end
def myread(bufio, bytes) # `bufio` is a StringIO object, `bytes` is bytes to read
begin
while bufio.size < bytes do
bufio.write($stdin.sysread(bytes - bufio.size))
end
rescue SignalException, Interrupt, Errno::EINTR => e
retry
rescue SystemCallError, IOError, EOFError => e
# nothing, we're done
end
end