K’s diary

プログラミング、ビジネスや時事ニュース、経営/人事、音楽や映画について書いていきます

エラー「...mysql2/client.rb:90: [BUG] Segmentation fault at 0x00000000000000」(セグフォ/セグメンテーション違反)に対応した

はじめに

前回の記事で、過去に作成したプロジェクトをローカルで立ち上げた際に発生したMySQLエラーに対応しました。今回はその続きです。

というのも、前回のMYSQLエラーを対処している途中で、同時に別のプロジェクトでMYSQLに関連するエラーが発生しました。これが今回のエラーですが、調べると前回のMYSQLエラーと関連していたので、記録として記載しておきます。

記事の対象者(Who)

  • Ruby on rails 初学者
  • 同じエラーが発生して対処に困っている方

開発環境

何をするか(What)

Ruby on rails プロジェクトで、以下のMySQLエラーが発生したので対処した。

vendor/bundle/ruby/2.5.0/gems/mysql2-0.5.2/lib/mysql2/client.rb:90: [BUG] Segmentation fault at 0x00000000000000

なぜ(Why)

  • エラーの原因を理解し、再発防止することと、次回発生時の対策のため
  • 同様のエラー報告はネット上での情報が少なかったため、同様の事象に困る人の助けになれば

エラーの原因を分析してみる

前回の記事で、7ヵ月程前に学習用に作成したアプリケーションを編集する際にエラー発生し対応。

このエラー発生前後のタイミングで新規作成したまま放置していたプロジェクトを、確認のために立ち上げた際に今回のエラーが発生した。

$ bundle install

vendor/bundle/ruby/2.5.0/gems/mysql2-0.5.2/lib/mysql2/client.rb:90: [BUG] Segmentation fault at 0x00000000000000
・・・中略・・・
-- Crash Report log information ------------
   See Crash Report log file under the one of following:                    
     * ~/Library/Logs/DiagnosticReports                                     
     * /Library/Logs/DiagnosticReports                                      
   for more details.                                                        
Don't forget to include the above Crash Report log file in bug reports.     
---------------------------------------------------------------

今回はエラーではなく思いっきり「BUG」と出ている。「クラッシュレポートをログファイルと一緒に出してくれ!」と言われています。

でもクラッシュレポートを出すのも面倒そうだし、タイミング的にもこれはMYSQLエラーに関連するエラーだろうと直感したので、少し自分で調べて出来るだけ対応してみることにしました。

まずは、 [BUG] Segmentation fault について調べてみました。

Segmentation fault(セグメンテーション違反)は、以下のような場合に発生する。

・「アクセスが許可されていないメモリ上の位置」へのアクセス

・「アクセスが許可されていない方法」での「メモリ上の位置」へのアクセス

 (リードオンリー位置への書き込み、オペレーティングシステムの上書きなど)

ちょっと小難しいですが、、、プログラムがメモリを使用する際(配列など)には、まずメモリを確保したうえでそのメモリにアクセスします。しかし、そのメモリ確保が不十分などの不備があり、確保した範囲を超えてメモリアクセスした場合にこのエラーが発生します。

ちなみに、Rubyはメモリ管理を自動的に行ってくれていますので、Rubyユーザーはこのメモリを意識せずに利用できます。その分、初学者にはこの「メモリ」の概念が少し分かりづらいかも知れませんね。興味がある方は調べてみて下さい。

参考:超初心者プログラミング入門「C言語」/メモリ操作

では次に、エラー箇所を特定してみます。エラー文を見ると以下の記載があります。

vendor/bundle/ruby/2.5.0/gems/mysql2-0.3.18/lib/mysql2/client.rb:90

ここがエラーが発生した箇所ですね。パスをよく見るとやはりMYSQL関連のエラーということがわかります。

しかし今回はシステムにインストールしているMYSQLではなく、プロジェクト内の「vendor」ディレクトリ下にあるgem mysql2のファイルのようです。むむむ。とにかく、gem mysql2のファイルを開いてみます。

# Correct the data types before passing these values down to the C level
user = user.to_s unless user.nil?
・・・中略・・・
socket = socket.to_s unless socket.nil?
conn_attrs = parse_connect_attrs(opts[:connect_attrs])

connect user, pass, host, port, database, socket, flags, conn_attrs ←ここがclient.rb:90
end

(詳細は確認しないとわかりませんが)MYSQLの認証周りのエラーのようですね。前回のエラーの時に調べて、MYSQLの5.0系と8.0系とでは認証の仕様が変更になっているため互換性が無いということが分かっています。ということは、やはり前回と類似のエラーということでしょうか。

また、以前のバージョンのgem mysql2の「...lib/mysql2/client.rb」の当該コードを見ると、「conn_attrs = parse_connect_attrs(opts[:connect_attrs])」の記述がありませんでした。ここが関係してるのかな??バージョンによって認証の仕様が異なっており、定義(メモリ確保)されていない変数を使おうとしてセグメントエラーが発生した、、、みたいなことでしょうか。この辺りに関してはまだまだ勉強不足です。

…と、色々検索していると「RubyDoc.info」のmysql2のREADMEに以下の記載がありました!

Mixing MySQL versions will generate segmentation faults.

Running the binary version of this gem against a different version of MySQL shared library libMySQL.dll will generate segmentation faults and terminate your application.

MySQLのバージョンを混在させると、セグメンテーション違反が発生します。

このバージョンのバイナリを、異なるバージョンのMySQL共有ライブラリ「libMySQL.dll」に対して実行すると、セグメンテーションフォルトを生成してアプリケーションが終了します。

う〜ん、やはり異なるバージョンのMySQLが混在していることで起こったエラーの様です。

という訳で、今回のエラーの対処法は前回のエラーの時と同じです。というか、同時期に、同じことが原因で発生したエラーでして、当然ながら同じ対処法で両方のエラーが改善されました。

(以下の手順は、前回の記事と同じです)

対処の手順(How)

  • 今回のエラーは以下の方針で対処しようと思います。
  1. 2つの異なるバージョンを混在させたくないので一方を残し、一方を削除する

  2. 5.6系へのclient libraryへのシンボルリンクで上書きしたい&過去に弄り倒したシステム環境を整理したい

よって、まずはMySQLを全て削除してしまい、5.6系のみを再インストールすることにした。

  • 実行手順は以下の通り
  1. データベースの保存

  2. mysql動作確認&停止

  3. MySQLのアンインストール(5.6/8.0共に)&関連ファイルの削除

  4. mysql@5.6」のインストール&MySQLの設定

実行手順の詳細

まずはデータベースの保存 (長くなるので補足説明しません)

$ mysqldump -u root -p -x --all-databases > 任意のファイル名dump.sql

念のため、「ps(プロセス)コマンド」で動作しているMySQLのプロセスを確認します。

$ ps -ax | grep mysql

50319 ?? 0:00.04 /bin/sh /usr/local/opt/mysql@5.6/bin/mysqld_safe --datadir=/usr/local/var/mysql
50416 ?? 0:23.45 /usr/local/opt/mysql@5.6/bin/mysqld --basedir=/usr/local/opt/mysql@5.6 --datadir=/usr/local/var/mysql --plugin-dir=/usr/local/opt/mysql@5.6/lib/plugin --log-error=<name>-MacBook-Air.local.err --pid-file=<name>-MacBook-Air.local.pid

<補足> 「ps」の後の「ax」はコマンドオプションです。 a:端末操作プロセス、x:端末操作以外のプロセスで、動作中のプロセスのを網羅的に指定 「grep」は、指定した文字(mysql)が含まれている行だけを抽出するコマンド

では動いているMySQLサーバーを止めておきましょう。

$ mysql.server stop

Shutting down MySQL
.... SUCCESS!

では、MySQLをアンインストールしていきます(どきどき)

$ brew uninstall mysql@5.6
$ brew uninstall mysql
$ brew cleanup

これでMySQLがアンインストールされました。

では関連ファイルを削除していきましょう!

$ sudo rm -rf /usr/local/var/mysql
$ sudo rm -rf /usr/local/var/mysql56/
$ sudo rm -rf /usr/local/mysql*

<補足> sudoは須藤ではなく、Unix系OSのプログラムで、主にユーザーがsuperuser(すなわちroot)の特権レベルを利用して操作をする際に用いるコマンド rmはremoveでファイルやディレクトリの削除コマンド -rfはオプションです。詳細は調べてみましょう!

また、残っている不要ファイルを削除していきましょう(できれば目視で探しながら)。

# Homebrewでインストールした場合生成されるファイルを探して消す
$ sudo rm -rf /usr/local/Cellar/mysql*
$ sudo rm -rf /usr/local/bin/mysql*
$ sudo rm -rf /usr/local/var/mysql*
$ sudo rm -rf /usr/local/etc/my.cnf
$ sudo rm -rf /usr/local/share/mysql*
$ sudo rm -rf /usr/local/opt/mysql*
$ sudo rm -rf /etc/my.cnf

# その他の関連ファイルは、以下の環境設定ファイルもあれば探して削除しておきましょう
$ sudo rm -rf /Library/Receipts/mysql*
$ sudo rm -rf /Library/Receipts/MySQL*
$ sudo rm -rf /Library/StartupItems/MySQLCOM
$ sudo rm -rf /Library/PreferencePanes/My*
$ sudo rm -rf /private/var/db/receipts/*mysql*
$ sudo rm ~/Library/LaunchDaemons/com.oracle.oss.mysql.mysqld.plist
$ sudo rm ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist

MySQL自動起動設定をしている場合はそちらも解除しておきましょう

$ launchctl unload -w ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist

<補足> PCを立ち上げると同時に自動でMySQLサーバーを立ち上げるための設定です。詳細は調べてみましょう!

できればここでPCを再起動して、全てのMySQLプロセスを終了しておきましょう。(私は再起動しませんでした。また反省)

それではいよいよ、mysql@5.6のインストールしていきます。

$ brew install mysql@5.6

無事にインストールできましたか?では、MySQLを設定をしていきます。

$ echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.bash_profile
$ export LDFLAGS="-L/usr/local/opt/mysql@5.6/lib"
$ export CPPFLAGS="-I/usr/local/opt/mysql@5.6/include"

最後に、MySQL自動起動の設定をしておきましょう。

$ ln -sfv /usr/local/opt/mysql\@5.6/*.plist ~/Library/LaunchAgents
$ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql@5.6.plist

完了ですね!ではMySQLサーバーを起動しましょう!

$ mysql.server start

Starting MySQL
... ERROR! The server quit without updating PID file (/usr/local/var/mysql/kanamaru-MacBook.local.pid).

!?

MySQLサーバーが立ち上がりません。ここで、念の為MySQLプロセスが動作していないか確認します。

$ ps aux | grep mysql

63646 0.0 0.2 4919136 8416 ?? S 8:54PM 0:01.40 /usr/local/opt/mysql@5.6/bin/mysqld --basedir=/usr/local/opt/mysql@5.6 --datadir=/usr/local/var/mysql --plugin-dir=/usr/local/opt/mysql@5.6/lib/plugin --log-error=MacBook-Air.local.err --pid-file=MacBook-Air.local.pid
63561 0.0 0.0 4279776 760 ?? S 8:54PM 0:00.03 /bin/sh /usr/local/opt/mysql@5.6/bin/mysqld_safe --datadir=/usr/local/var/mysql
69565 0.0 0.0 4295688 848 s000 S+ 9:43PM 0:00.01 grep mysql

なんか色々動いてそうなので、一旦全てのプロセスを強制終了します。

$ killall mysqld

そして、もう一度MySQLサーバーの起動にチャレンジ!

$ mysql.server start

Starting MySQL
SUCCESS!

動きました!ハァ〜。。。では、MySQLを入れ替えたので、一度データベースを再構築しようと思い、、

$ rake db:create

・・・中略・・・

rake aborted!
LoadError: dlopen(/Users/~/~/vendor/bundle/ruby/2.5.0/gems/mysql2-0.5.2/lib/mysql2/mysql2.bundle, 9): Library not loaded: /usr/local/opt/mysql/lib/libmysqlclient.21.dylib

はい、エラー出ました。エラー文をみると「clientlibrary.21」となっています。これはMySQL8.0系のものです(5.6系はclientlibrary.18)。

どういうことだ!?MySQLライブラリの関連ファイルを探しても「clientlibrary.21」は見つからず、正常に5.6系のみがインストールされています。

と、エラー文をよく見ると、、

.../Users/~/~/vendor/bundle/ruby/2.5.0/gems/mysql2-0.5.2/lib/mysql2/mysql2.bundle, 9)

とありますね。ここを直接見に行くとプロジェクトのgemファイル内にコンパイルされたMySQLライブラリでした。(これまで作業していたのはHomebrewでシステムにインストールされたMySQLライブラリ関連ファイルです。)

このコンパイルされたgemの中に「libmysqlclient.21.dylib」(mysql8.0系)へのリンクが生きているということでしょうか??何はともあれ、以下のコマンドで適切なライブラリへのシンボルリンクを貼ります。

$ sudo ln -s /usr/local/Cellar/mysql@5.6/5.6.43/lib/libmysqlclient.18.dylib /usr/lib/libmysqlclient.18.dylib

また「ln」はリンクの作成、「-s」はオプション「シンボルリンク(ショートカットみたいなもの)の作成」という意味になります。

「/usr/local/Cellar/mysql@5.6/5.6.43/lib/libmysqlclient.18.dylib」がリンク元のファイルのパス「/usr/lib/libmysqlclient.18.dylib」がシンボルリンクの名前です。

これで動きました!

その後、試しに新たにrails newして新規作成したプロジェクトも正常に動作することを確認。そして、過去に作成したプロジェクトも全て動き出しました!!

めでたしめでたし...

参考ページ

RubyDoc.info

MySQL公式

最後に

2回目の投稿、少し書く時間が短くなってきました。Markdown記法もだいぶ使える様になりました。

次回以降も主にプログラミング(RubyRuby on railsJavascript、Html/Cssなど)やAWSなどのサーバー関連、今回学習したMySQL、Homebrew、bundler、rbenvなど)や時事関連ネタを少しづつ書いていきたいと思います。

他にも、もう少しフランクな記事も作成していきたいと思っています(音楽、映画、禁煙日記など)