2018年5月5日土曜日

MT4 で Mail: not enough space for "Subject" と表示されてしまう場合の原因と対策

### 原因 エラーの理由は単純で、64個の mail queue がいっぱいになったということです。 > 参考 > - [Mail: not enough space for "............. subject.........." - General - MQL5 programming forum](https://www.mql5.com/en/forum/46410) Queue というのは窓口の列みたいなものです。 (マ○ドナルドのレジが1つしかない状況を想像してください) MT4 のメール用 Queue は 64通しか保持することができないので、それ以上のメール送信リクエストがくると、このエラーが起こります。 もう少し解説すると、`SendMail()` を実行した時点でメールは一度この Queue に入ります。 一般的には、この Queue に入った時点で送信処理が行われ、送信成功した場合は Queue のメールはなくなります。 この最初のメールの送信処理が終わる前に次のメールが来た場合、2通目として Queuing されます。 さらにメールの送信依頼が来た場合、さらに Queue に追加されます。
Queue - [Wikipedia より By Vegpuff - CC 表示-継承 3.0](https://commons.wikimedia.org/wiki/File:Data_Queue.svg)
この列に64通以上のメールがたまり、さらに送信依頼が来た場合、次のメールは送信されず破棄され、このエラーが出るというわけです。 これが起こるのは送信処理能力より多くのメールが来た時ですが、さらに原因を2つに分類することが出来ます。 - サーバーエラー等でメールが送れない場合
いわば、レジの人が居ないような状態レジの人がぶっ倒れるとも言える?です。 - 短期間に大量のメールを送ろうとした場合
毎 tick にメール送信依頼をしたような場合です。
どんな優秀なレジの人でも、一気に大量のお客さんが来た場合は列がのびるということですね。 2つめはメール送信するタイミングを見直すことで改善可能ですが、1つめは回避しようがありません。 ### 復旧する方法がない?
2019/4/10 追記
MT4 Build 1170 (20 Dec 2018) で動作確認したところ、自然復旧することもあるようです。 どういった条件で復旧するのかまだ不明ですが、確認できたらまた追記するつもりです。
~~この問題の厄介なところは、**一度 Queue 溢れが起こると、MT4 を再起動するしか復旧させる方法がない**(と思われるいろいろ試したのですが、他の方法を見つけられなかった)ことです。~~ ~~つまり、一度このエラーが起こった後、順調にメールが処理されて Queue に残ったメールが無くなったとしても、次にメールを送ろうとした場合、このエラーが発生するということです。~~ なので、このエラー自体を起こさないようにすることが非常に重要です。 そこで、参考リンクのアイデアを元に対策を考えてみました。 ### 対策1 メール送信頻度を下げる 当然といえば当然ですが、メール送信は重大なエラー等が起こった時だけにし、頻繁に行わないことが重要です。 ### 対策2 エラー処理をきちんとする 例えば、以下のようにメール送信エラー発生から `MAIL_INTERVAL` 間はメールを送らないようにするような方法が考えられます。 メールが送れなかった場合は `SendNotification()` で Notification を送るようにしてみました場合によってはどちらも送れない可能性もあるので、本当はログ等にも出した方がよい。 なお、コメントに日本語を書いていますが、これはブログ用に追記したもので、私は基本的には MQL4 で日本語は書かないようにしています。 > 参考 > > [MQL4 内で日本語は使わない方が良い | Strategy of C](https://strategyofc.blogspot.com/2018/05/mql4.html) ```mq4 bool send_mail(string subject, string content) { static datetime last_send_mail_error_time = 0; // 0ならエラー発生していないとする bool is_succeeded = false; if (last_send_mail_error_time == 0 || TimeLocal() - last_send_mail_error_time > MAIL_INTERVAL) { // メールエラーが発生していないか、メールエラーが発生してから MAIL_INTERVAL 以上経過していたらメールを送ってみる is_succeeded = SendMail(subject, content); if (is_succeeded) { last_send_mail_error_time = 0; } else { // メール送信に失敗した場合は、その時間を保存 last_send_mail_error_time = TimeLocal(); SendNotification("mail send error"); } } else { // 最後のメール送信失敗から MAIL_INTERVAL 以内にメールを送ろうとした場合 SendNotification("Couldn't send mail because of the last error"); } return is_succeeded; } ``` `MAIL_INTERVAL` は、以下のように定数定義してあるものとします。(5分間の例) ```mq4 static const unsigned long MAIL_INTERVAL = 5 * 60; // 5 minuets ``` > 参考 > > datetime の差分は秒になる > > [[MQL4] datetime を long にキャストすると 1970/1/1 からの秒になる | Strategy of C](https://strategyofc.blogspot.com/2018/05/mql4-datetime-long-197011.html) この方法でも 5分×65 = 325分間サーバーに繋げなかった場合は、"Mail: not enough space for" が出てしまいます。もうこれは回避しようがないと思われます。 可能なら、"Mail: not enough space for" エラーを別途検知したかったのですが、メール送信エラー時に [GetLastError](https://docs.mql4.com/check/getlasterror) で取得できるエラーは 全て "ERR_SEND_MAIL_ERROR (4061) - Send mail error" のようですエラー番号の意味なし。 > 参考 > > [Runtime Errors - Codes of Errors and Warnings - Standard Constants, Enumerations and Structures - MQL4 Reference](https://docs.mql4.com/constants/errorswarnings/errorcodes) ### 最後は Notification で確認 なるべく発生しないようにしていても、絶対に発生しないことを保証出来ないのがこの問題の難しいところ。 その場合、 上記の例のように Notification を利用してエラーの発生を知らせると良いと思います。 ネットワークが完全に死んでる場合はこのエラーも通知されませんが、そうでなければ、問題が起こったことはすぐわかります。 可能であれば、手動で MT4 を再起動すれば良いのです。 え?検知して MT4 を自動で再起動すれば良い? 確かにそれも可能ですが、稼働中の EA があったりする場合悪影響が。 だったらメールが送られてこないくらい我慢したほうが良いのではないでしょうか?