macOS Sierraにしたらrustが暴走して、黒い画面には入れなくなった #解決済み
思わぬ原因不明の落とし穴に落ちた
macOS Sierraにしたらrustが暴走して、Terminal.appが立ち上がるも入力のプロンプトが いつまでたっても(10分くらい待ってたw)表示されなくなりました。
負荷も高い状態がずっと続き、Spotlight経由でEmacsを動かして、M-x shellで中を見ようとするも、 こちらも立ち上がらず、一時、途方にくれましたが、
アクティビティモニタを確認すると、rustcが複数実行され、そろぞれCPUを90%以上使った状態でした。 (ターミナルのタブ数+emacsのrustcが実行されてる感じでした。)
ここで、ターミナル上でCtrl+Cしたら、rustcが終了して、無事にプロンプトが現れ、コマンドラインが復活しました。
なんとなく、ちょと前に導入された、ターミナルでの作業状態を復活させる機能に絡んで 何か悪さをされている気がしたので、過去のセッション情報を
rm ~/.bash_sessions/*
で消すも、状態変わらず、
bash -l
しても、同様にrustcが実行されてしまう状態のまま。
原因不明だが、Rustの再インストールで解決した模様
コマンドラインが使えない状態は早急に解決したかったので、
curl -sSf https://static.rust-lang.org/rustup.sh | sh -s -- --uninstall
して、一旦rustを削除して、terminal.appを再度立ち上げ、正常にログインできていることを 確認して、rustを再びインストールしたところ、これで治ったようです。
関連記事
cordovaのプラグインを作ってkindle fireでmrubyを動かしてみた
4千円しないAndroidタブレットを活用しないのはもったいない
Amazonプライムデーで4千円ださずに購入したkindle file。長男が操作をいつのまにか覚え、車のゲームを あれこれDLして遊んでるところを奥さんに見つかり、大目玉を喰らってます。
今回はそんなkindleにmrubyを動かそうとした記録です。
準備編
Androidの開発はプライベートでは既に奥さんの仕事向けにWebViewをつかったやつを大昔のEclipseで 作成したことがあるくらいなので、ぼんやりとは把握してるつもり。そして、イマドキはEclipseではなく、 Android Studioで行うのが一般的らしいというのは予習済み。
Android Studioの環境作成
OSXで主に今回の作業を進めた。Windows 10でも一部やったが、OracleのJDKを入れて、 Android Studioをインストールすれば、すぐに動く感じ。 (Android SDK、NDKのDLや仮想マシンのDLでかなり時間は掛かけど。)
SDKやNDKもAndroid Studioからインストール出来、これを使って作業をしている。
Android Studioを離れての作業でも、これらのパスを押さえておけば、大丈夫そうだった。
mrubyの組み込み
素のmrubyをAndroid向けにビルドするには簡単。
「mrubyをAndroid Studioのエミュレーターで動かすためのlibmruby.aを作る - Qiita」に書いたようにlibmruby.aを作って、 これをJNIな.soファイルにリンクして、呼び出せばOK。
挫折編
素のmrubyが簡単に組み込めたので、mruby-cliでのクロスコンパイルに対応してあるmruby-webcamを 組み込めば、
cam = Webcam.new cam.capture {|img| # img : JPEG format puts img.length } cam.snap
と言った具合に簡単にkindle fireのカメラにアクセス出来ると思い、作業を始めた。
現行のFireOSですら、カメラのアクセスはJavaを経由しないと難しい模様。 Nでは許可を得るのにJava必須な模様なので、FireOSがアップデートされたら、こちらもJavaが必須となりそう。
OpenCV無駄に古い2.7系を静的リンクしてるから更新せねば mrubyを動かすcordovaのプラグイン少し進んだ。 #cordova #mruby pic.twitter.com/8f634ENhOk
— kjunichi (@kjunichi) 2016年9月17日
Cordova(Android)編
当初はNDKを使えば、C言語で頑張れば、色々出来るから、それらを上手いこと mrbgemにすれば楽しそう!と考えていましたが、基本Javaを介さないとAndoridのサービス?等を 利用することができなそうなことが分かってきたので、方針転換
- C->Javaしなきゃ行けないなら、Java->JavaScriptも使ってしまえ! -> Cordovaがあるのか!
putsとgetsの実装
RubyPicoが目標だが
puts,getsが実装できれば、最低限mrubyを動かして、プログラムの実験が出来るのではと思い始めた 。すでにこの段階で、mrubyには2.7系ではあるが、opencvがリンクしてあり、mruby-webcamを 少し改造すれば、カメラで撮影した画像を顔検出位まではmrubyで出来そうな見込みであり、 早々にputs,getsを実装したいことろだった。
先人であるRubyPicoのコードが大いに参考になった。 Rubyはクラスの書換えも自由自在なので、puts、getsを書き換えることが簡単にでき、
- putsではDOMにdivタグで囲まれたテキストを追加する実装に変更
- getsではalertをwindow.promptで入力を取得する実装に変更
といった実装で進めた。
putsとgetsの実装がなんとなく出来そうな気がしてきたから気が早いが、リポジトリ確保w / “GitHub - kjunichi/cordova-plugin-mruby: mruby for cordova” https://t.co/bYJw1yPmcJ
— kjunichi (@kjunichi) 2016年9月15日
躓きポイント(puts実装時)
JavaからJavaScriptを呼ぶには
事前にAndroidでは昔からWebView#loadUrlはあまり望ましくないとの情報を 得ており、WebView#evaluateJavascriptがKitKat以降は使えるとの事だったが、
CordovaのAndroidでは簡単には使えない模様。loadUrlは使えたので、こちらでとりあえず実装を進めた。
mrubyを動かすcordovaプラグイン、putsの実装にハマった。java,c,mruby,javascriptと言語を色々使っての実装なのでなかなかこんがらがりそう https://t.co/oyoiDn6Y6P
— kjunichi (@kjunichi) 2016年9月18日
といった具合にputsですら苦戦して、getsなどは、スレッド絡んでホントに大変だった。
躓きポイント(gets実装時)
WebView#evaluateJavascriptが使えれば、window.promptへの入力後に呼んでもらうコールバックを設定できて 苦労がなかったはずだが、それが使えなかったので、プラグインのコードでwindow.promptの入力を待つのに苦戦
CordovaのプラグインはUIスレッドで動いているのを理解しておらず、 Thread.sleepして固まった。 これに対応する為に、別スレッドで動かし、window.prompt入力後に Js側のコールバックを実行するように非同期処理にして、getsが出来た。
やっと、uiスレッドと別のスレッドを非同期に動かして、getsの実装が出来た! #cordova #mruby pic.twitter.com/BRj2DnpwLL
— kjunichi (@kjunichi) 2016年9月19日
final CallbackContext cb = callbackContext; if (action.equals("mrbLoadString")) { final String script = args.getString(0); Thread th = new Thread(new Runnable(){ @Override public void run() { String jniString = MrubyJni.mrbLoadString(script); Mruby.setResult(jniString); String message = ". JNI says: " + _jniString; Mruby.mrbLoadString(message, cb); } }); th.start(); return true; } public static String gets() { Log.d("Mruby", "gets"); waitInput = true; inputString = "dummy"; final String js = "console.log(mruby.__jsGets())"; stdout.getView().post(new Runnable() { public void run(){ //stdout.sendJavascript(js); Log.d("Mruby", "gets/exec js = " + js); stdout.loadUrl("javascript:" + js); Log.d("Mruby", "gets/exec js done"); } }); Log.d("Mruby", "gets/wait"); while (waitInput) { try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } Log.d("Mruby", "gets/rtn = "+inputString); return inputString; }
成果物
役に立ったこと
デバッグ文代わりのLogとCのマクロが役に立った。 コマンドラインでcordova runコマンドで動かして、AndroidStudioで適当なプロジェクト開いておくと、 出力したログが勝手に表示されて便利だった。
import android.util.Log; Log.d("Mruby", "gets/wait");
__android_log_print(ANDROID_LOG_DEBUG,"Init", "Java_com_hatenablog_abrakatabura_mruby_MrubyJni_initialize");
IDE環境でのCordovaのプラグイン作成方法が不明なので、improt文含めコピペ。
まとめ
去年書いた記事の「外部のライブラリに依存するmrbgemを使ってもmruby-cliでワンバイナリを作成できるようにした」の作業を通して、クロスビルドの経験が出来ているので、あまり臆せずNDK絡んでの開発が行えた気がする。経験大事。
また、HT-03Aの頃と違い、NDK関連も検索すれば、やりたいことに近い事例や、エラーメッセージへの 対応等サクサク出てて来る。ただ、kindle fireだとそもそもAndroidのバージョンいくつと同等なのか とか、それなりに取り組まないと把握が難しいと感じた。やり出せば、楽。ちなみに現時点では5.1のlollipop相当らしい。
ndk-buildコマンド or Android Studioのプラグイン
コマンドラインで生活してる場合、ndk-buildコマンドが楽な知見。
プラグインの設定が面倒。過去になんどか書き方が変更になっているので、検索に注意が必要。
Cordovaの感想
Cordova使ってみて、さくっと子供たちのゲーム教育用アプリを作ってkindleやAndroid端末で動かせるので、 お父さんはIT系の仕事やってる感を前面に押し出せそうで、心強いw。
ドキュメントがググると日本語で見つかるが、ザ機械翻訳なので、Androidが人造人間と訳されているなど、 読みづらい。とはいえ、無いよりずっと良い。
また、頻繁にAPIも変更なっているようで、既に出回っているプラグインのソースを呼んで 使い方を理解する等が出来ないと辛いが、それが出来れば、良い感じで楽できそうだった。
今後の課題
現状ではmrubyをEmscriptenでJsに変換して動かすWebrubyと比べて、なんらメリットがなさそうなので、 当初のカメラを使って簡単な画像処理あたりまでの実装。
あと、CordovaでiOSも試した。去年から無料で実機にデプロイ出来るようになったので、 実機で動かせた。iOS10も出たばかりで、こちらの新しい気のも試したい感はある。
参考資料
関連記事
- OpenCVをEmscriptenでJS化して動かせた
- Xperia SO-01Bで使用できるOpenGL ESのバージョンは?
- 端末の方向を変えたときに画面が毎回初期化される - 明日の鍵
- eclipseでAndroidアプリが動かなくなった
- VAIO type P でAndroidを動かす ~前編~
- VAIO type P でAndroidを動かす ~後編~
- VAIO type PでFroyoが動いた!
- type P gingerbreadへの道
- gingerbread-x86がtype Pで動いたが。。
- gingerbread-x86がtype PでWifi通信も可能に
1年後の記事
mrubyでHTTP/1.1のKeep-Aliveで複数回リクエストを出してみた
サンプルが無い問題
そもそも、HTTPクライアントの例がHTTP/1.0だったり、あってもHTTP/1.1でConnection: close指定という パターンに世の中は溢れていた。
KeepAliveにするも、2回目のリクエストの応答が来ない
s = TCPSocket.open("127.0.0.1", 8080) s.write("GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n") puts s.read puts "<----" s.write("GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n") puts s.read s.close
としたが、2回目のリクエストが貰えない。 当初、発行出来たのかも分からなかった。
そこで、自分が割とつかえるNode.jsでHTTPサーバーをサンプルコード 切り貼りで作成した。
//server.js const http = require('http'); //httpモジュールのインポート const server = http.createServer(); server.on('request', function(req, res) { console.log("onRequest"); const myhtml = "<html><body><a href=\"/\">link</a></body></html>" res.writeHead(200, {'Content-Type': 'text/html',/*'Content-Length': myhtml.length,*/'Connection':'keep-alive'}); res.write(myhtml);// resの中身を出力 res.end(); }) server.on('connection', function(socket) { console.log("A new connection was made by a client."); socket.setTimeout(30 * 1000); // 30 second timeout. Change this as you see fit. }) server.listen(8080,"0.0.0.0")
node.jsで作成したサーバー側を念のため、Firefoxでアクセスして、サーバー側はそれなりに動いていることを 確認
Socket#recv
一晩寝たら、CのAPIのrecvを思いだし、Socket#readの代わりにSocket#recvを使えば、行けそうなことが分かった。 Socket#readは接続がクローズされるまでブロックされるから、駄目だったのだ。
Transfer-Encodeing: chunked
node.jsで標準のモジュールでhttpサーバーを作ると、Connection: keep-aliveだと 応答のTransfer-Encodingがchunked指定で返されることが判明。
Content-Length方式なら、楽勝だったのに、まぁ、node.jsならこっちで返す方が、node.jsらしいけど。
chunkedな応答への対応
Transfer-Encodingがchunkedの場合、Body部が以下の形式で返される。
16進数で長さ\r\n 指定された長さの文字列\r\n
0\r\nの有無だけで駄目
いちいち、長さを見て云々より、最終行は0\r\nだから、これの有無を見つければ、良いかというと、 以下のパターンで破綻する。
1\r\n 0\r\n 10\r\n 0123456789abcdef\r\n 0\r\n
というパターンもあり、結局Body部の先頭から地道にパースするしかなさそう。
mrubyで16進数を10進数に変換
Integer("0xff")
でCRubyと同様に10進数に変換出来た。
h2oで確認
h2oの場合、静的コンテンツはContent-Length方式で応答が返された。もしかすると、 ファイルサイズによってはchunked方式で返されるのかもしてないが。。。
また、hhvmを動かし試したら、こちらはchunkedでnode.jsの時は0xffの形式だったが、 phpinfoで試したこともあり、0xffff形式で、返ってくることが分かった。
mrubyにHTTP::Parser
mrubyにHTTP::Parserがあることに気付くが、 \r\n\r\nまで取得できた状態で、パースすると、メソッドがGETに設定されて 返ってきて、結局自前である程度パースして、サーバーからの応答を取得後 こちらを使って、細かなヘッダー部のパースをお願いする使い方が吉なのか?
成果物
def readSomeChunkedData(s,data,body) chunks = data.split("\r\n") if chunks[0] == "0" return true else for idx in 0..chunks.size-1 next if idx % 2 == 1 return if chunks[idx] == "0" hex = "0x" + chunks[idx] readSize = Integer(hex)-chunks[idx+1].length chunks[idx+1] << s.recv(readSize+2) if readSize > 0 body[0] << chunks[idx+1] end end return false end def readChunkedData(s,data) body=[""] until readSomeChunkedData(s,data,body) data = s.recv(1024) end body[0] end def readBody(s,data,hdr) if hdr[:isChunked] body = readChunkedData(s, data) else # Content-Length readSize = hdr[:contentLength] - data.length data << s.recv(readSize) if readSize >0 body = data end body end def analyzeHeader(header) headers = header.split("\r\n") for h in headers do h.downcase! if h.index("content-length:") == 0 contentLength = h.split(":")[1].to_i end if h.index("transfer-encoding:" ) == 0 if h.index("chunked") >0 isChunked = true end end end {:isChunked => isChunked, :contentLength => contentLength} end def readHeader(s, data) buf = s.recv(1024) data << buf if buf.index("\r\n\r\n") == nil readHeader(s,data) end data end def parse(s,data) if data.index("\r\n\r\n") == nil data = readHeader(s, data) end header = data.split("\r\n\r\n") p header rtn=analyzeHeader(header[0]) if header.size == 1 body = readBody(s, s.recv(1024),rtn) else body = readBody(s,header[1],rtn) end body end s = TCPSocket.open("127.0.0.1", 8080) s.write("POST /hhvm/test.php HTTP/1.1\r\nHost: localhost\r\nContent-Length: 3\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nA=b") s.write("GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n") puts parse(s,s.recv(256)) puts "<----" s.write("GET /hhvm/test.php HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n") puts parse(s,s.recv(2)) s.close
まとめ
mrubyというか、mruby-socketでHTTP/1.1のKeep-aliveでのコネクションを維持して複数回リクエストをサーバに 投げることができた。
Keep-aliveだとサーバからの応答をそれなりにパースする必要があり、世の中にサンプルが見つからなかったのも、 まぁ、やってみてなんとなく分かった気がする。
Socket#gets的なものを実装した方が、良かった気もするが、それだと、recvの呼び出し回数が増えそうだったので、 やめた。 (\r\nがあるか。recvで確認して、無ければ、サイズ増やして、再度確認、見つかったら、そこまでrecvするといった 具合で、recvの呼び出し回数が増えるのではと)
もしかして、速すぎる最適ってやつかもw。