childprocess 0.2.8 → 0.2.9

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.
data/.travis.yml CHANGED
@@ -3,7 +3,13 @@ rvm:
3
3
  - 1.9.2
4
4
  - 1.9.3
5
5
  - jruby
6
- # - rbx
6
+ - rbx
7
7
  env:
8
8
  - CHILDPROCESS_POSIX_SPAWN=true
9
- - CHILDPROCESS_POSIX_SPAWN=false
9
+ - CHILDPROCESS_POSIX_SPAWN=false
10
+ matrix:
11
+ exclude:
12
+ - rvm: rbx
13
+ env: CHILDPROCESS_POSIX_SPAWN=true
14
+ - rvm: jruby
15
+ env: CHILDPROCESS_POSIX_SPAWN=true
data/README.md CHANGED
@@ -47,7 +47,7 @@ Implementation
47
47
 
48
48
  How the process is launched and killed depends on the platform:
49
49
 
50
- * Unix : fork + exec
50
+ * Unix : fork + exec (or posix_spawn if enabled)
51
51
  * Windows : CreateProcess and friends
52
52
  * JRuby : java.lang.{Process,ProcessBuilder}
53
53
 
data/lib/childprocess.rb CHANGED
@@ -10,17 +10,17 @@ module ChildProcess
10
10
 
11
11
  class << self
12
12
  def new(*args)
13
- case platform
14
- when :jruby
15
- JRuby::Process.new(args)
16
- when :windows
17
- Windows::Process.new(args)
13
+ case os
18
14
  when :macosx, :linux, :unix, :cygwin
19
15
  if posix_spawn?
20
16
  Unix::PosixSpawnProcess.new(args)
17
+ elsif jruby?
18
+ JRuby::Process.new(args)
21
19
  else
22
20
  Unix::ForkExecProcess.new(args)
23
21
  end
22
+ when :windows
23
+ Windows::Process.new(args)
24
24
  else
25
25
  raise Error, "unsupported platform #{platform.inspect}"
26
26
  end
@@ -32,10 +32,6 @@ module ChildProcess
32
32
  :jruby
33
33
  elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == "ironruby"
34
34
  :ironruby
35
- elsif RUBY_PLATFORM =~ /mswin|msys|mingw32/
36
- :windows
37
- elsif RUBY_PLATFORM =~ /cygwin/
38
- :cygwin
39
35
  else
40
36
  os
41
37
  end
@@ -46,7 +42,7 @@ module ChildProcess
46
42
  end
47
43
 
48
44
  def unix?
49
- !jruby? && [:macosx, :linux, :unix].include?(os)
45
+ !windows?
50
46
  end
51
47
 
52
48
  def linux?
@@ -57,14 +53,12 @@ module ChildProcess
57
53
  platform == :jruby
58
54
  end
59
55
 
60
- def jruby_on_unix?
61
- jruby? and [:macosx, :linux, :unix].include? os
62
- end
63
-
64
56
  def windows?
65
- !jruby? && os == :windows
57
+ os == :windows
66
58
  end
67
59
 
60
+ @posix_spawn = false
61
+
68
62
  def posix_spawn?
69
63
  enabled = @posix_spawn || %w[1 true].include?(ENV['CHILDPROCESS_POSIX_SPAWN'])
70
64
  return false unless enabled
@@ -85,6 +79,10 @@ module ChildProcess
85
79
  false
86
80
  end
87
81
 
82
+ #
83
+ # Set this to true to enable experimental use of posix_spawn.
84
+ #
85
+
88
86
  def posix_spawn=(bool)
89
87
  @posix_spawn = bool
90
88
  end
@@ -95,12 +93,14 @@ module ChildProcess
95
93
  host_os = RbConfig::CONFIG['host_os'].downcase
96
94
 
97
95
  case host_os
98
- when /mswin|msys|mingw32|cygwin/
99
- :windows
100
- when /darwin|mac os/
101
- :macosx
102
96
  when /linux/
103
97
  :linux
98
+ when /darwin|mac os/
99
+ :macosx
100
+ when /mswin|msys|mingw32/
101
+ :windows
102
+ when /cygwin/
103
+ :cygwin
104
104
  when /solaris|bsd/
105
105
  :unix
106
106
  else
@@ -161,3 +161,5 @@ module ChildProcess
161
161
 
162
162
  end # class << self
163
163
  end # ChildProcess
164
+
165
+ require 'jruby' if ChildProcess.jruby?
@@ -2,15 +2,15 @@ module ChildProcess
2
2
  class Error < StandardError; end
3
3
  class TimeoutError < StandardError; end
4
4
  class SubclassResponsibility < StandardError; end
5
- class InvalidEnvironmentVariableName < StandardError; end
5
+ class InvalidEnvironmentVariable < StandardError; end
6
6
  class LaunchError < StandardError; end
7
7
 
8
8
  class MissingPlatformError < StandardError
9
9
  def initialize
10
10
  platform = ChildProcess.platform_name
11
11
 
12
- message = "posix_spawn is not yet supported on #{ChildProcess.platform_name} (#{RUBY_PLATFORM}), falling back to fork/exec. " +
13
- "Please file a bug at http://github.com/jarib/childprocess/issues"
12
+ message = "posix_spawn is not yet supported on #{ChildProcess.platform_name} (#{RUBY_PLATFORM}), falling back to default implementation. " +
13
+ "If you believe this is an error, please file a bug at http://github.com/jarib/childprocess/issues"
14
14
 
15
15
  super(message)
16
16
  end
@@ -1,9 +1,56 @@
1
+ require 'java'
2
+ require 'jruby'
3
+
4
+ class Java::SunNioCh::FileChannelImpl
5
+ field_reader :fd
6
+ end
7
+
8
+ class Java::JavaIo::FileDescriptor
9
+ if ChildProcess.os == :windows
10
+ field_reader :handle
11
+ else
12
+ field_reader :fd
13
+ end
14
+ end
15
+
1
16
  module ChildProcess
2
17
  module JRuby
18
+ def self.posix_fileno_for(obj)
19
+ channel = ::JRuby.reference(obj).channel
20
+ begin
21
+ channel.getFDVal
22
+ rescue NoMethodError
23
+ fileno = channel.fd
24
+ if fileno.kind_of?(Java::JavaIo::FileDescriptor)
25
+ fileno = fileno.fd
26
+ end
27
+
28
+ fileno == -1 ? obj.fileno : fileno
29
+ end
30
+ rescue
31
+ # fall back
32
+ obj.fileno
33
+ end
34
+
35
+ def self.windows_handle_for(obj)
36
+ channel = ::JRuby.reference(obj).channel
37
+ fileno = obj.fileno
38
+
39
+ begin
40
+ fileno = channel.getFDVal
41
+ rescue NoMethodError
42
+ fileno = channel.fd if channel.respond_to?(:fd)
43
+ end
44
+
45
+ if fileno.kind_of? Java::JavaIo::FileDescriptor
46
+ fileno.handle
47
+ else
48
+ Windows::Lib.handle_for fileno
49
+ end
50
+ end
3
51
  end
4
52
  end
5
53
 
6
- require "java"
7
54
  require "childprocess/jruby/pump"
8
55
  require "childprocess/jruby/io"
9
- require "childprocess/jruby/process"
56
+ require "childprocess/jruby/process"
@@ -46,6 +46,7 @@ module ChildProcess
46
46
  end
47
47
 
48
48
  def fetch_size(type_name, opts = {})
49
+ print "sizeof(#{type_name}): "
49
50
  src = <<-EOF
50
51
  int main() {
51
52
  printf("%d", (unsigned int)sizeof(#{type_name}));
@@ -59,10 +60,14 @@ int main() {
59
60
  raise "sizeof(#{type_name}) == #{output.to_i} (output=#{output})"
60
61
  end
61
62
 
62
- @sizeof[type_name] = output.to_i
63
+ size = output.to_i
64
+ @sizeof[type_name] = size
65
+
66
+ puts size
63
67
  end
64
68
 
65
69
  def fetch_constant(name, opts)
70
+ print "#{name}: "
66
71
  src = <<-EOF
67
72
  int main() {
68
73
  printf("%d", (unsigned int)#{name});
@@ -71,12 +76,14 @@ int main() {
71
76
  EOF
72
77
 
73
78
  output = execute(src, opts)
74
- @constants[name] = Integer(output)
79
+ value = Integer(output)
80
+ @constants[name] = value
81
+
82
+ puts value
75
83
  end
76
84
 
77
85
 
78
86
  def execute(src, opts)
79
-
80
87
  program = Array(opts[:define]).map do |key, value|
81
88
  <<-SRC
82
89
  #ifndef #{key}
@@ -1,10 +1,19 @@
1
+
1
2
  module ChildProcess
2
3
  module Unix
3
4
  module Lib
4
5
  extend FFI::Library
5
-
6
6
  ffi_lib FFI::Library::LIBC
7
7
 
8
+ if ChildProcess.os == :macosx
9
+ attach_function :_NSGetEnviron, [], :pointer
10
+ def self.environ
11
+ _NSGetEnviron().read_pointer
12
+ end
13
+ elsif respond_to? :attach_variable
14
+ attach_variable :environ, :pointer
15
+ end
16
+
8
17
  attach_function :strerror, [:int], :string
9
18
 
10
19
  # int posix_spawnp(
@@ -1,7 +1,8 @@
1
1
  module ChildProcess::Unix::Platform
2
2
  SIZEOF = {
3
3
  :posix_spawn_file_actions_t => 80,
4
- :posix_spawnattr_t => 336
4
+ :posix_spawnattr_t => 336,
5
+ :sigset_t => 128
5
6
  }
6
7
  POSIX_SPAWN_RESETIDS = 1
7
8
  POSIX_SPAWN_SETPGROUP = 2
@@ -13,22 +13,22 @@ module ChildProcess
13
13
 
14
14
  if @io
15
15
  if @io.stdout
16
- actions.add_dup @io.stdout.fileno, $stdout.fileno
16
+ actions.add_dup fileno_for(@io.stdout), fileno_for($stdout)
17
17
  else
18
- actions.add_open $stdout.fileno, "/dev/null", File::WRONLY, 0644
18
+ actions.add_open fileno_for($stdout), "/dev/null", File::WRONLY, 0644
19
19
  end
20
20
 
21
21
  if @io.stderr
22
- actions.add_dup @io.stderr.fileno, $stderr.fileno
22
+ actions.add_dup fileno_for(@io.stderr), fileno_for($stderr)
23
23
  else
24
- actions.add_open $stderr.fileno, "/dev/null", File::WRONLY, 0644
24
+ actions.add_open fileno_for($stderr), "/dev/null", File::WRONLY, 0644
25
25
  end
26
26
  end
27
27
 
28
28
  if duplex?
29
29
  reader, writer = ::IO.pipe
30
- actions.add_dup reader.fileno, $stdin.fileno
31
- actions.add_close writer.fileno
30
+ actions.add_dup fileno_for(reader), fileno_for($stdin)
31
+ actions.add_close fileno_for(writer)
32
32
  end
33
33
 
34
34
  if defined? Platform::POSIX_SPAWN_USEVFORK
@@ -37,13 +37,17 @@ module ChildProcess
37
37
 
38
38
  attrs.flags = flags
39
39
 
40
+ # wrap in helper classes in order to avoid GC'ed pointers
41
+ argv = Argv.new(@args)
42
+ envp = Envp.new(ENV.to_hash.merge(@environment))
43
+
40
44
  ret = Lib.posix_spawnp(
41
45
  pid_ptr,
42
46
  @args.first, # TODO: not sure this matches exec() behaviour
43
47
  actions,
44
48
  attrs,
45
49
  argv,
46
- env
50
+ envp
47
51
  )
48
52
 
49
53
  if duplex?
@@ -62,38 +66,58 @@ module ChildProcess
62
66
  ::Process.detach(@pid) if detach?
63
67
  end
64
68
 
65
- def argv
66
- arg_ptrs = @args.map do |e|
67
- if e.include?("\0")
68
- raise ArgumentError, "argument cannot contain null bytes: #{e.inspect}"
69
+ if ChildProcess.jruby?
70
+ def fileno_for(obj)
71
+ ChildProcess::JRuby.posix_fileno_for(obj)
72
+ end
73
+ else
74
+ def fileno_for(obj)
75
+ obj.fileno
76
+ end
77
+ end
78
+
79
+ class Argv
80
+ def initialize(args)
81
+ @ptrs = args.map do |e|
82
+ if e.include?("\0")
83
+ raise ArgumentError, "argument cannot contain null bytes: #{e.inspect}"
84
+ end
85
+
86
+ FFI::MemoryPointer.from_string(e.to_s)
69
87
  end
70
- FFI::MemoryPointer.from_string(e.to_s)
88
+
89
+ @ptrs << nil
71
90
  end
72
- arg_ptrs << nil
73
91
 
74
- argv = FFI::MemoryPointer.new(:pointer, arg_ptrs.size)
75
- argv.put_array_of_pointer(0, arg_ptrs)
92
+ def to_ptr
93
+ argv = FFI::MemoryPointer.new(:pointer, @ptrs.size)
94
+ argv.put_array_of_pointer(0, @ptrs)
76
95
 
77
- argv
78
- end
96
+ argv
97
+ end
98
+ end # Argv
79
99
 
80
- def env
81
- env_ptrs = ENV.to_hash.merge(@environment).map do |key, val|
82
- if key.include?("=")
83
- raise InvalidEnvironmentVariableName, key
100
+ class Envp
101
+ def initialize(env)
102
+ @ptrs = env.map do |key, val|
103
+ if key =~ /=|\0/ || val.include?("\0")
104
+ raise InvalidEnvironmentVariable, "#{key.inspect} => #{val.inspect}"
105
+ end
106
+
107
+ FFI::MemoryPointer.from_string("#{key}=#{val}")
84
108
  end
85
109
 
86
- FFI::MemoryPointer.from_string("#{key}=#{val}")
110
+ @ptrs << nil
87
111
  end
88
112
 
89
- env_ptrs << nil
90
-
91
- env = FFI::MemoryPointer.new(:pointer, env_ptrs.size)
92
- env.put_array_of_pointer(0, env_ptrs)
113
+ def to_ptr
114
+ env = FFI::MemoryPointer.new(:pointer, @ptrs.size)
115
+ env.put_array_of_pointer(0, @ptrs)
93
116
 
94
- env
95
- end
117
+ env
118
+ end
119
+ end # Envp
96
120
 
97
121
  end
98
122
  end
99
- end
123
+ end
@@ -1,3 +1,3 @@
1
1
  module ChildProcess
2
- VERSION = "0.2.8"
2
+ VERSION = "0.2.9"
3
3
  end
@@ -252,12 +252,20 @@ module ChildProcess
252
252
  )
253
253
 
254
254
  str = buf.read_string(size).strip
255
- "#{str} (#{errnum})"
255
+ if errnum == 0
256
+ "Unknown error (Windows says #{str.inspect}, but it did not.)"
257
+ else
258
+ "#{str} (#{errnum})"
259
+ end
256
260
  end
257
261
 
258
262
  def handle_for(fd_or_io)
259
263
  if fd_or_io.kind_of?(IO) || fd_or_io.respond_to?(:fileno)
260
- handle = get_osfhandle(fd_or_io.fileno)
264
+ if ChildProcess.jruby?
265
+ handle = ChildProcess::JRuby.windows_handle_for(fd_or_io)
266
+ else
267
+ handle = get_osfhandle(fd_or_io.fileno)
268
+ end
261
269
  elsif fd_or_io.kind_of?(Fixnum)
262
270
  handle = get_osfhandle(fd_or_io)
263
271
  elsif fd_or_io.respond_to?(:to_io)
@@ -48,12 +48,12 @@ module ChildProcess
48
48
  strings = ENV.map { |k,v| "#{k}=#{v}\0" }
49
49
 
50
50
  # extras
51
- @environment.each do |key, value|
52
- if key.include?("=")
53
- raise InvalidEnvironmentVariableName, key
51
+ @environment.each do |key, val|
52
+ if key =~ /=|\0/ || val.include?("\0")
53
+ raise InvalidEnvironmentVariable, "#{key.inspect} => #{val.inspect}"
54
54
  end
55
55
 
56
- strings << "#{key}=#{value}\0"
56
+ strings << "#{key}=#{val}\0"
57
57
  end
58
58
 
59
59
  strings << "\0" # terminate the env block
data/spec/io_spec.rb CHANGED
@@ -85,10 +85,10 @@ describe ChildProcess do
85
85
  # http://travis-ci.org/#!/jarib/childprocess/jobs/487331
86
86
  #
87
87
 
88
- it "works with pipes", :jruby => false do
88
+ it "works with pipes", :process_builder => false do
89
89
  process = ruby(<<-CODE)
90
- STDOUT.puts "stdout"
91
- STDERR.puts "stderr"
90
+ STDOUT.print "stdout"
91
+ STDERR.print "stderr"
92
92
  CODE
93
93
 
94
94
  stdout, stdout_w = IO.pipe
@@ -109,12 +109,15 @@ describe ChildProcess do
109
109
  stdout_w.close
110
110
  stderr_w.close
111
111
 
112
- stdout.read.should == "stdout\n"
113
- stderr.read.should == "stderr\n"
112
+ out = stdout.read
113
+ err = stderr.read
114
+
115
+ [out, err].should == %w[stdout stderr]
114
116
  end
115
117
 
116
118
  it "can set close-on-exec when IO is inherited" do
117
- server = TCPServer.new("localhost", 4433)
119
+ port = random_free_port
120
+ server = TCPServer.new("127.0.0.1", port)
118
121
  ChildProcess.close_on_exec server
119
122
 
120
123
  process = sleeping_ruby
@@ -123,6 +126,6 @@ describe ChildProcess do
123
126
  process.start
124
127
  server.close
125
128
 
126
- lambda { TCPServer.new("localhost", 4433).close }.should_not raise_error
129
+ wait_until { can_bind? "127.0.0.1", port }
127
130
  end
128
131
  end
data/spec/jruby_spec.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
2
  require "pid_behavior"
3
3
 
4
- if ChildProcess.jruby?
4
+ if ChildProcess.jruby? && !ChildProcess.windows?
5
5
  describe ChildProcess::JRuby::IO do
6
6
  let(:io) { ChildProcess::JRuby::IO.new }
7
7
 
@@ -11,7 +11,7 @@ if ChildProcess.jruby?
11
11
  end
12
12
 
13
13
  describe ChildProcess::JRuby::Process do
14
- if ChildProcess.jruby_on_unix?
14
+ if ChildProcess.unix?
15
15
  it_behaves_like "a platform that provides the child's pid"
16
16
  else
17
17
  it "raises an error when trying to access the child's pid" do
data/spec/spec_helper.rb CHANGED
@@ -115,6 +115,32 @@ module ChildProcessSpecHelper
115
115
  10
116
116
  end
117
117
 
118
+ def random_free_port
119
+ server = TCPServer.new('127.0.0.1', 0)
120
+ port = server.addr[1]
121
+ server.close
122
+
123
+ port
124
+ end
125
+
126
+ def wait_until(timeout = 10, &blk)
127
+ end_time = Time.now + timeout
128
+
129
+ until Time.now >= end_time
130
+ return if yield
131
+ sleep 0.05
132
+ end
133
+
134
+ raise "timed out"
135
+ end
136
+
137
+ def can_bind?(host, port)
138
+ TCPServer.new(host, port).close
139
+ true
140
+ rescue
141
+ false
142
+ end
143
+
118
144
  end # ChildProcessSpecHelper
119
145
 
120
146
  Thread.abort_on_exception = true
@@ -125,8 +151,8 @@ RSpec.configure do |c|
125
151
  @process && @process.alive? && @process.stop
126
152
  }
127
153
 
128
- if defined?(JRUBY_VERSION)
129
- c.filter_run_excluding :jruby => false
154
+ if ChildProcess.jruby? && !ChildProcess.posix_spawn?
155
+ c.filter_run_excluding :process_builder => false
130
156
  end
131
157
 
132
158
  if ChildProcess.linux? && ChildProcess.posix_spawn?
data/spec/unix_spec.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
2
  require "pid_behavior"
3
3
 
4
- if ChildProcess.unix? && !ChildProcess.posix_spawn?
4
+ if ChildProcess.unix? && !ChildProcess.jruby? && !ChildProcess.posix_spawn?
5
+
5
6
  describe ChildProcess::Unix::Process do
6
7
  it_behaves_like "a platform that provides the child's pid"
7
8
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: childprocess
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
4
+ hash: 5
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 8
10
- version: 0.2.8
9
+ - 9
10
+ version: 0.2.9
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jari Bakken
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-01-09 00:00:00 Z
18
+ date: 2012-01-12 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rspec