As chamadas fork(2)
ficam mais lentas à medida que o processo pai usa mais memória devido à necessidade de copiar tabelas de páginas. Em muitos usos comuns de fork(), onde ele é seguido por uma das funções da família exec para gerar processos filhos ( Kernel#system
, IO::popen
, Process::spawn
, etc.), é possível remover essa sobrecarga usando interfaces especiais de geração de processos ( posix_spawn()
, vfork()
, etc.)
A biblioteca posix-spawn visa implementar um subconjunto da interface Ruby 1.9 Process::spawn
de uma forma que aproveite as interfaces de geração rápida de processos quando disponíveis e forneça substitutos sensatos em sistemas que não o fazem.
Process::spawn
do Ruby 1.9 e versões aprimoradas de Kernel#system
, Kernel#`
, etc. em Ruby >= 1.8.7 (atualmente apenas MRI).POSIX::Spawn::Child
de alto nível para cenários IPC rápidos (mas corretos!) de não streaming. Os benchmarks a seguir ilustram o tempo necessário para bifurcar/executar um processo filho com tamanhos crescentes de memória residente no Linux 2.6 e MacOS X. Os testes foram executados usando o programa posix-spawn-benchmark
incluído no pacote.
posix_spawn
é mais rápido que fork+exec
e executa em tempo constante quando usado com POSIX_SPAWN_USEVFORK
.
fork+exec
é extremamente lento para processos pai grandes.
posix_spawn
é mais rápido que fork+exec
, mas nenhum deles é afetado pelo tamanho do processo pai.
Esta biblioteca inclui duas interfaces distintas: POSIX::Spawn::spawn
, uma interface de geração de processo de nível inferior baseada no novo método Ruby 1.9 Process::spawn
, e POSIX::Spawn::Child
, uma classe de nível superior voltada para fácil geração de processos com manipulação de fluxo de entrada/saída/erro padrão simples baseado em string. O primeiro é muito mais versátil, o último requer muito menos código para determinados cenários comuns.
O módulo POSIX::Spawn
(com a ajuda da extensão C que o acompanha) implementa um subconjunto da interface Ruby 1.9 Process::spawn, em grande parte através do uso das interfaces de sistemas IEEE Std 1003.1 posix_spawn(2)
. Eles são amplamente suportados por vários sistemas operacionais UNIX.
Em sua forma mais simples, o método POSIX::Spawn::spawn
pode ser usado para executar um processo filho semelhante a Kernel#system
:
require 'posix/spawn'
pid = POSIX::Spawn::spawn('echo', 'hello world')
stat = Process::waitpid(pid)
A primeira linha executa echo
com um único argumento e retorna imediatamente o pid
do novo processo. A segunda linha aguarda a conclusão do processo e retorna um objeto Process::Status
. Observe que spawn
não espera que o processo termine a execução como system
e não obtém o status de saída do filho - você deve chamar Process::waitpid
(ou equivalente) ou o processo se tornará um zumbi.
O método spawn
é capaz de realizar um grande número de operações adicionais, desde a configuração do ambiente do novo processo, até a alteração do diretório de trabalho do filho, até o redirecionamento de descritores de arquivos arbitrários.
Consulte a documentação Process::spawn
do Ruby 1.9 para obter detalhes e a seção STATUS
abaixo para obter um relato completo dos vários recursos Process::spawn
suportados por POSIX::Spawn::spawn
.
system
, popen4
e `
Além do método spawn
, implementações compatíveis com Ruby 1.9 de Kernel#system
e Kernel#`
são fornecidas no módulo POSIX::Spawn
. O método popen4
pode ser usado para gerar um processo com objetos stdin, stdout e stderr redirecionados.
O módulo POSIX::Spawn
também pode ser misturado a classes e módulos para incluir spawn
e todos os métodos utilitários nesse namespace:
require 'posix/spawn'
class YourGreatClass
include POSIX::Spawn
def speak(message)
pid = spawn('echo', message)
Process::waitpid(pid)
end
def calculate(expression)
pid, in, out, err = popen4('bc')
in.write(expression)
in.close
out.read
ensure
[in, out, err].each { |io| io.close if !io.closed? }
Process::waitpid(pid)
end
end
A classe POSIX::Spawn::Child
inclui lógica para executar processos filhos e ler/gravar a partir de seus fluxos de entrada, saída e erro padrão. Ele foi projetado para receber todas as entradas em uma única string e fornecer todas as saídas como strings únicas e, portanto, não é adequado para transmitir grandes quantidades de dados dentro e fora de comandos. Dito isto, tem alguns benefícios:
select(2)
) - trata todos os casos de travamento de pipe devido a exceder os limites PIPE_BUF
em um ou mais fluxos. POSIX::Spawn::Child
pega os argumentos spawn
padrão quando instanciado e executa o processo até a conclusão após escrever todas as entradas e ler todas as saídas:
>> require 'posix/spawn'
>> child = POSIX::Spawn::Child.new('git', '--help')
Recupere a saída do processo gravada em stdout/stderr ou inspecione o status de saída do processo:
>> child.out
=> "usage: git [--version] [--exec-path[=GIT_EXEC_PATH]]n ..."
>> child.err
=> ""
>> child.status
=> #<Process::Status: pid=80718,exited(0)>
Use a opção :input
para gravar dados no stdin do novo processo imediatamente após a geração:
>> child = POSIX::Spawn::Child.new('bc', :input => '40 + 2')
>> child.out
"42n"
Opções adicionais podem ser usadas para especificar o tamanho máximo de saída ( :max
) e o tempo de execução ( :timeout
) antes que o processo filho seja abortado. Consulte a documentação POSIX::Spawn::Child
para obter mais informações.
POSIX::Spawn::Child.new
gera o processo imediatamente quando instanciado. Como resultado, se for interrompido por uma exceção (seja por atingir o tamanho máximo de saída, o limite de tempo ou outro fator), não será possível acessar os resultados out
ou err
porque o construtor não foi concluído.
Se você deseja obter os dados out
and err
que estavam disponíveis quando o processo foi interrompido, use o formulário alternativo POSIX::Spawn::Child.build
para criar o filho sem gerar o processo imediatamente. Ligue exec!
para executar o comando em um local onde você possa capturar quaisquer exceções:
>> child = POSIX::Spawn::Child.build('git', 'log', :max => 100)
>> begin
?> child.exec!
?> rescue POSIX::Spawn::MaximumOutputExceeded
?> # limit was reached
?> end
>> child.out
"commit fa54abe139fd045bf6dc1cc259c0f4c06a9285bbn..."
Observe que quando a exceção MaximumOutputExceeded
é gerada, os dados reais combinados out
e err
podem ser um pouco maiores que o valor :max
devido ao buffer interno.
O método POSIX::Spawn::spawn
foi projetado para ser o mais compatível possível com Process::spawn
do Ruby 1.9. No momento, é um subconjunto compatível.
Esses argumentos Process::spawn
são atualmente suportados por qualquer um dos Spawn::spawn
, Spawn::system
, Spawn::popen4
e Spawn::Child.new
:
env: hash
name => val : set the environment variable
name => nil : unset the environment variable
command...:
commandline : command line string which is passed to a shell
cmdname, arg1, ... : command name and one or more arguments (no shell)
[cmdname, argv0], arg1, ... : command name, argv[0] and zero or more arguments (no shell)
options: hash
clearing environment variables:
:unsetenv_others => true : clear environment variables except specified by env
:unsetenv_others => false : don't clear (default)
current directory:
:chdir => str : Not thread-safe when using posix_spawn (see below)
process group:
:pgroup => true or 0 : make a new process group
:pgroup => pgid : join to specified process group
:pgroup => nil : don't change the process group (default)
redirection:
key:
FD : single file descriptor in child process
[FD, FD, ...] : multiple file descriptor in child process
value:
FD : redirect to the file descriptor in parent process
:close : close the file descriptor in child process
string : redirect to file with open(string, "r" or "w")
[string] : redirect to file with open(string, File::RDONLY)
[string, open_mode] : redirect to file with open(string, open_mode, 0644)
[string, open_mode, perm] : redirect to file with open(string, open_mode, perm)
FD is one of follows
:in : the file descriptor 0 which is the standard input
:out : the file descriptor 1 which is the standard output
:err : the file descriptor 2 which is the standard error
integer : the file descriptor of specified the integer
io : the file descriptor specified as io.fileno
Atualmente, estas opções NÃO são suportadas:
options: hash
resource limit: resourcename is core, cpu, data, etc. See Process.setrlimit.
:rlimit_resourcename => limit
:rlimit_resourcename => [cur_limit, max_limit]
umask:
:umask => int
redirection:
value:
[:child, FD] : redirect to the redirected file descriptor
file descriptor inheritance: close non-redirected non-standard fds (3, 4, 5, ...) or not
:close_others => false : inherit fds (default for system and exec)
:close_others => true : don't inherit (default for spawn and IO.popen)
A opção :chdir
fornecida por Posix::Spawn::Child, Posix::Spawn#spawn, Posix::Spawn#system e Posix::Spawn#popen4 não é thread-safe porque os processos gerados com a chamada de sistema posix_spawn(2) herdar o diretório de trabalho do processo de chamada. A gema posix-spawn contorna essa limitação na chamada do sistema, alterando o diretório de trabalho do processo de chamada imediatamente antes e depois de gerar o processo filho.
Direitos autorais (c) de Ryan Tomayko e Aman Gupta.
Consulte o arquivo COPYING
para obter mais informações sobre licença e redistribuição.