You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Perl qx在Mojolicious::Lite中挂起但普通脚本正常?

搞定Mojolicious路由里执行tr命令挂起的问题

我来帮你分析下这个诡异的现象,顺便给几个靠谱的解决办法:

为啥普通Perl脚本没问题,放到Mojolicious里就挂?

核心问题出在管道命令的信号处理和Mojolicious的事件循环环境上。

你用的命令链是tr -cd "[:alnum:]" < /dev/urandom | fold -w3 | head -n1,正常逻辑是:head读到第一行就退出,这时候fold会收到SIGPIPE信号(因为后面的管道没人读了),接着fold退出,tr也会收到SIGPIPE,停止读/dev/urandom,整个命令链就结束了。

但在Mojolicious的路由处理函数里,事件循环会接管进程的标准输入输出处理,导致SIGPIPE信号没有被正确传递给tr进程。tr根本不知道后面的fold和head已经跑没影了,还一个劲地从/dev/urandom读数据,自然就占着CPU不撒手,直到你手动中断。你测试cat也挂,就是因为同样的管道信号问题。

怎么解决?给你三个方案,按需选:

方案1:用Mojolicious自带的非阻塞子进程API

Mojolicious专门提供了Mojo::IOLoop::Subprocess来处理这种需要调用外部命令的场景,既能避免阻塞事件循环,还能正确处理进程信号:

use Mojolicious::Lite;
use strict;
use warnings;
use Data::Dumper;

post 'testit' => sub {
  my $c = shift;
  my $pass_length = 3;
  
  # 启动子进程执行命令,不阻塞主事件循环
  Mojo::IOLoop::Subprocess->new->run(
    # 第一个回调:在子进程里执行命令
    sub {
      my $exec = qq{tr -cd '[:alnum:]' < /dev/urandom | fold -w$pass_length | head -n1};
      my $pass = `$exec`;
      chomp $pass;
      return $pass;
    },
    # 第二个回调:子进程完成后处理结果
    sub {
      my ($subprocess, $err, $pass) = @_;
      if ($err) {
        warn "生成密码出错:$err";
        return $c->render(json => {error => '密码生成失败'});
      }
      warn Dumper $pass;
      return $c->render(json => {foo => 'bar', password => $pass});
    }
  );
  
  # 告诉框架这个请求要异步处理,别直接返回
  $c->render_later;
};

app->secrets('foobar');
app->start;

方案2:修改命令,限制tr的读取量

既然tr会无限读,那我们给它加个限制,用head先读足够的随机字节再传给tr,这样tr处理完就自动退出:

my $exec = qq{head -c 100 /dev/urandom | tr -cd '[:alnum:]' | fold -w$pass_length | head -n1};

100字节足够生成N位的字母数字密码了,完全够用。

方案3:纯Perl生成随机密码(最推荐)

调用外部命令本来就容易出环境兼容问题,不如直接用Perl代码生成,高效又靠谱:

use Mojolicious::Lite;
use strict;
use warnings;
use Data::Dumper;
use List::Util qw(sample); # Perl 5.20+自带,比shuffle更高效

post 'testit' => sub {
  my $c = shift;
  my $pass_length = 3;
  # 定义所有可用的字母数字字符
  my @chars = ('a'..'z', 'A'..'Z', 0..9);
  # 随机选$pass_length个字符拼接成密码
  my $pass = join '', sample($pass_length, @chars);
  
  # 要是追求更安全的随机(比如符合密码学安全),可以直接读/dev/urandom:
  # open my $urand_fh, '<:raw', '/dev/urandom' or die "打不开/dev/urandom: $!";
  # read $urand_fh, my $buf, $pass_length;
  # $pass = unpack('H*', $buf); # 转成十六进制字符串,或者自己映射成字母数字
  
  warn Dumper $pass;
  return $c->render(json => {foo => 'bar', password => $pass});
};

app->secrets('foobar');
app->start;

最后总结下

这个坑的本质是Mojolicious事件循环环境下,外部管道命令的SIGPIPE信号传递异常,导致tr进程无法感知后续进程退出,进而无限读取/dev/urandom。三个方案里,纯Perl生成是最优解,避免了外部命令的依赖;要是非得用外部命令,就用Mojolicious的子进程API更稳妥。

内容的提问来源于stack exchange,提问作者ftumsh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 17:28:09