childprocess 0.2.8 → 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
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