質問するログイン新規登録
Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

1回答

1199閲覧

try~catch内で時間で処理を終了させる

arashiyama

総合スコア33

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

0クリップ

投稿2022/02/19 14:17

編集2022/02/20 03:18

0

0

try catch内でexecの処理が長引いたときに時間経過で強制終了させる

node.jsでdiscordのメッセージからのコードを実行させ、処理が5秒を超えたときに強制終了させる方法

発生している問題

処理を止めるにはthrowかreturnしかない(多分)
時間差で実行時間するにはsetTimeoutを使うしかない
setTimeoutの引数は関数なのでreturnしても意味ない
setTimeoutすると別のコールスタックにいく(多分)のでthrowしてもcatchできない

該当のソースコード

javascript

1//モジュール等の有効化 2client.on("messageCreate", async message => { 3 if (message.author.id === "1234") { 4 if (message.content.startsWith("js\n")) { 5 let body =message.content.substring(3) 6 let log 7 try{ 8 fs.writeFileSync("sample.js", body) 9 const before = Date.now() 10 setTimeout(() => { 11 throw "実行時間が5秒を超えたため強制終了しました" 12 }, 5000); 13 log=execSync("node sample.js").toString()||"出力なし" 14 message.reply("```js\n" + log + "```\n実行時間:" + (Date.now() - before) / 1000 + "秒") 15 }catch(e){ 16 message.reply(e+"") 17 } 18    return 19 } 20 } 21}

結果

11行目のthrowが15行目でcatchされずに全体に投げられコードが止まる

試したこと

execでやる
fsで書き出したjsファイルに5秒でエラーを吐くようにする

補足情報(FW/ツールのバージョンなど)

nodejs v17.5.0
"child_process": "^1.0.2",
"discord.js": "^13.6.0",

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

おっと、これはasync/awaitの表現の限界ですね。

async関数を宣言するとthrow等の挙動がPromiseのrejectを叩くような挙動に変化しますが、
別のコールバック関数の中身に入ると、
そういったasync関数特有の挙動の効果が消えます。

なので普通にJS上でエラーを投げただけになります。

そしてsetTimeout(fn, 5000)の中でthrowしているのが問題で、
JavaScriptが持っているイベント置き場に「実行して関数」と「達成条件」のセットを設置して(イベント登録)
動作終了後にイベントを達成しているかな?と巡回して関数を実行してくれるイベントループというフェイズにて実行してもらう挙動をします。

なのでasync関数以外の非同期関数内でthrowを投げるとcatch出来ずに即座にエラー落ちします。
try-catchつかえねぇ……

というわけで何かしらの別手段をひねり出す必要があります。


じゃあどうすればええねん
考えられる大まかなプランとしては2つあります。

  • child_processの中でも後からコマンド実行状況に割り込めるspawnを使う
  • Promiseインスタンスをasync関数による暗黙の生成に頼るのではなく、自分で生成して引数であるreject関数を確保する

今回はタイムアウトを実現したい。
つまり一定時間を経過したらコマンド実行に割り込んで動作を停止させる必要があるので
前者のspawnを使うのが正解です。

なのでPromiseを捨てて従来の非同期処理のイベントエミッターを使う事になります。
spawn関数の実行の仕方は公式ドキュメントを見れば理解出来ますね。

その返り値の扱い方から見ていきましょう。
ドキュメントのReturnsの項目を見ると、ChildProcessというインスタンスを返すようです。
これはイベントエミッターを継承して作られたインスタンスなので、onやらemit等の機能が利用できます。

Returns: <ChildProcess>
Extends: <EventEmitter>

ですが、その中でも着目すべきはsubprocess.exitCodesubprocess.kill()でしょう。

The subprocess.exitCode property indicates the exit code of the child process. If the child process is still running, the field will be null.
和訳: subprocess.exitCodeプロパティは、子プロセスの終了コードを示します。子プロセスがまだ実行中の場合、フィールドはnullになります。

ここまで見えてれば後は楽ですね。
質問文のコードを利用するとこんな感じになります。

js

1const {spawn} = require("child_process"); 2 3client.on("messageCreate", message => { 4 if (message.author.id === "1234") { 5 if (message.content.startsWith("js\n")) { 6 let body =message.content.substring(3); 7 let log = ""; 8 9 fs.writeFileSync("sample.js", body) 10 const ls = spawn("node sample.js"); 11 ls.on("data", m => log = log + m); // 文字列は小出しに来るので都度log引数に吸い出す 12 ls.on("close", code => { 13 if (code == 0) { 14 log = log || "出力なし"; 15 message.reply("```\n" + log + "```\n実行時間:" + (Date.now() - before) / 1000 + "秒"); // console.logの表示内容はJavaScriptのコードというわけではないのでjsは削った 16 return; 17 } 18 message.reply("実行時間が5秒を超えたため強制終了しました"); 19 }); 20 setTimeout(() => { 21 // exitCodeがnullならば、まだ実行中と見なしてプロセスを終了させる 22 if (ls.exitCode == null) ls.kill("SIGINT"); 23 }, 5000); 24 } 25 } 26}

動作検証はしてないので少し不安ですが、
考え方自体はこれで行けると思うので
検証込で実行してみてください。

投稿2022/02/20 04:34

編集2022/02/20 08:01
miyabi-sun

総合スコア21482

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.30%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問