1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

macOS Tahoeの49.7日問題を観察する

1
Last updated at Posted at 2026-07-26

macOS Tahoeを連続で49.7日間稼働させ続けると、TCPネットワーク接続に不具合が発生するバグが、最近話題になってます。いつも使っているMac miniのuptimeが45日を超えてきたので、49日目前後の挙動を観察しました。結論としては、デフォルト設定(自動でスリープ)を使う状況では、問題発生しませんでした。

49.7日問題

macOSの稼働時間が49.7日を超えると、ネットに接続できなくなるという問題(以下、49.7日問題と呼ぶことにします)が話題になってます。

macOSのTCPスタックに、tcp_nowという変数があり、これがシステム起動時からの経過時間をミリ秒で保持しているそうです。この変数が符号無し32ビット( uint32_t )で定義されているので、232 - 1ミリ秒経過するとオーバーフローしてしまいます。秒にすると4,294,967.295秒、約49.7日になります。この制約に適切に対処しないと、TCP関連の不具合を引き起こしそうな気がします。その一つがTahoeにおけるTIME_WAIT状態未解消の不具合です。

下図は、TCPの状態遷移図です。中央下部にTIME WAITという状態を示す箱があります。49.7日以降には、この状態から脱出できない接続が増大してしまうとのことです。

(Wikipedia commonsより)

少し詳しく説明すると、確立されている(ESTABLISHED)接続を、こちらから閉じる(Active CLOSE)する場合に、最後にTIME_WAITという状態になり、遅延したパケット処理などのために、60秒ほど様子を見ます(macOSの場合、タイムアウトは30秒という記述もありました)。このタイムアウトの計算にtcp_nowが使われているそうです。なので、49.7日経過すると、TIME_WAITになった接続が閉じられることなく溜まってしまい、新たなTCP接続ができなくなるとのことです。

先行検証事例

Mac miniをwebサーバーとして運用していて、49.7日問題に遭遇した方の報告がZennにあります。49.7日目前後のTIME_WAIT数などを調査されています。

それによると、49.7日を超えると、TIME_WAIT数が単調増加し、最終的には2万個前後まで増え、ポートの空きが大きく減り、新しいTCP接続が軒並み失敗したそうです。私のMac miniも、電源入れっぱなしで毎日使っているので、同様の不具合が発生したら困ると思いました。それで実際に確かめることにしました。

使用するMacとmacOS

以前、Macをサーバーにして長期間使用していたこともあったのですが、実際にこの不具合を気にしたことはなかったです。macOS Tahoe限定の不具合らしいという情報もあるので、昔は問題なかったのかもしれないです。システムアップデートで再起動するから気づかないという説もありますし、スリープを使用すると発生しずらいという情報もあります。

実証に使用するMacは、Webを見たり、メールを読み書きするなどして毎日使っているM4 Mac miniです。電源は落とさず、放置しています。システム設定のエネルギーは、macOSのデフォルトのままです。

energy.png

このデフォルト設定では、Macを放置するとスリープに入ります。ただスリープ中も、iCloudの同期、メッセージとメールの受信などを行っているので、頻繁にネットワークアクセスしています。ただスリープ中は、外部からの接続には応答できません。なのでサーバーとして使用するためには、「ディスプレイがオフの時に自動でスリープさせない」の項目をOnにします。前述のZennの検証では、この項目がOnになっていたはずです。今回の検証では、この項目をデフォルトのまま、Offで行います。

macOSは、Tahoe 26.5.1です。現在、26.5.2が配布されていますが、アップデートを止めて稼働し続けてます。26.5.1公開日から26.5.2公開日は28日経過しているので、それぞれに自動アップデートして再起動していれば、49.7日目に至ることはないです。

このminiは、メモリ32GB, SSD256GBと、メモリ全振り構成で購入しました。でもAliExpressで内蔵SSDモジュールを買って交換したので、2TBに増強されてます。交換した1年ほど前は、2TB SSDが3万円以下でしたが、昨今のメモリ高騰で 今は7万円くらいします。

記録の準備

このMac miniの執筆時点の稼働時間を、uptimeコマンドで見ると以下のようです。

% uptime
13:39  up 46 days,  9:03, 3 users, load averages: 2.99 2.45 1.78

まもなく49.7日になります。49.7日目を超えると、TIME_WAIT状態の接続が増大してしまうとのことなので、TIME_WAIT状態にある接続が何個あるのかを記録していくことにしました。

接続の一覧は、netstatコマンドで得られます。このうち、TIME_WAIT状態にあるものをgrepで抽出して数えてみたところ、この時は1個ありました。何度か試したところ、0個だったり、3個、6個など色々でした。

% netstat -na | grep -c TIME_WAIT
1

この個数を、ログファイルに書き込んでいくことにしました。他にも、タイムスタンプと、稼働秒数、稼働日数をログファイルに書き込みたいと思います。それらをカンマで区切っておけば、表計算ソフトでグラフ化しやすいです。

タイムスタンプは、測定時のUNIX時刻(1970年1月1日0時0分0秒からの秒数)としました。dateコマンドで以下のようにオプションを付ければ、現在のUNIX時刻秒が得られます。

% date +%s
1784436292

稼働時間は、uptimeコマンドとか、これにオプションをつけてuptime --libxo jsonなどすれば得られますが、パースするのが面倒だったので、システムブート時刻から計算することにしました。

システムをブートした時刻は、以下のコマンドで得られます。6月3日の早朝4:37だったようです。ちなみに、Tahoe 26.5.1の公開日は日本時間で6月2日だったので、直後の夜間に自動アップデートされ、再起動したものと思われます。

% sysctl kern.boottime
kern.boottime: { sec = 1780429042, usec = 953470 } Wed Jun  3 04:37:22 2026

また、ここに表示された1780429042秒が、ブートしたUNIX時刻の秒数のようです。この数値と、現在のUNIX時刻の差を計算すれば、システムの稼働時間がわかります。それを60x60x24=86400で割れば、稼働日数になります。なので、次のshellスクリプトで、これらをカンマ区切りで表示させることができます。(この後の手順で、cronで動かそうとしたところ、netstatへのパスが通ってなかったので、フルパスで記述してあります。)

#! /bin/bash
UNIXTIME=$(date +%s)
UPSEC=$(( $UNIXTIME - 1780429042 ))
UPDAYS=$(( $UPSEC / 86400 ))
TIMEWAIT=$(/usr/sbin/netstat -na | grep -c TIME_WAIT)
echo "$UNIXTIME, $UPSEC, $UPDAYS, $TIMEWAIT"

これをtimewait.shという名前で保存して、実行すると、

% ./timewait.sh 
1784435739, 4006697, 46, 1

というように、UNIX時刻、稼働秒、稼働日数、TIME_WAIT数が表示されました。そして、crontabに以下の情報を書き込んで、10分ごとにログファイルへ追記していくよう設定しました。(ユーザ名はhogeにしてあります)

% crontab -l
*/10 * * * * /Users/hoge/timewait.sh >> /Users/hoge/log

この結果、以下のようなログファイルが得られます。1行に、600秒ごとのUNIX時刻、稼働秒、稼働日数、TIME_WAIT数が記録されてます。カンマ区切りなので表計算しやすいです。

% tail log
1784434800, 4005758, 46, 0
1784435400, 4006358, 46, 0
1784436000, 4006958, 46, 0
1784436600, 4007558, 46, 1
1784437200, 4008158, 46, 0
1784437800, 4008758, 46, 1
1784438401, 4009359, 46, 0
1784439000, 4009958, 46, 0
1784439600, 4010558, 46, 1
1784440200, 4011158, 46, 0

とうとう49.7日になった

crontabを設定してしばらくしたら、49.7日目が過ぎました。tcp_nowの最大値である、4,294,967秒前後のログを以下に示します。

UNIX時刻(秒) 稼働(秒)  日  TIME_WAIT
1784719200, 4290158, 49, 4
1784719800, 4290758, 49, 3
1784720400, 4291358, 49, 5
1784721000, 4291958, 49, 3
1784721600, 4292558, 49, 3
1784722200, 4293158, 49, 5
1784722800, 4293758, 49, 4
1784723401, 4294359, 49, 3
1784724000, 4294958, 49, 4 <-----ここが49.7日
1784724600, 4295558, 49, 3
1784725200, 4296158, 49, 3
1784725800, 4296758, 49, 4
1784726400, 4297358, 49, 3
1784727000, 4297958, 49, 3
1784727601, 4298559, 49, 4
1784728200, 4299158, 49, 3
1784728800, 4299758, 49, 3
1784729400, 4300358, 49, 4
1784730000, 4300958, 49, 5
1784730600, 4301558, 49, 3
1784731200, 4302158, 49, 2
1784731800, 4302758, 49, 4
1784732400, 4303358, 49, 3

なんと、予想に反して平常運転です。4,294,967秒後からは、TIME_WAITが減ることはなくなり、蓄積されていくと予想していたのですが、ちゃんと減少してます。

46日頃のTIME_WAIT数に0が多かったのに対して、49.7日付近では、3前後が目立つようにも見えます。しかし、その後も計測を続けたところ、52日頃からは、0が多くなりました。

1784921400, 4492358, 51, 0
1784922000, 4492958, 52, 0
1784923200, 4494158, 52, 0
1784926200, 4497158, 52, 0
1784928001, 4498959, 52, 3
1784929200, 4500158, 52, 0
1784930401, 4501359, 52, 5
1784932200, 4503158, 52, 0
1784934601, 4505559, 52, 0
1784935800, 4506758, 52, 0
1784937600, 4508558, 52, 0
1784939400, 4510358, 52, 0
1784941200, 4512158, 52, 0
1784941802, 4512760, 52, 1

グラフにすると以下のようになります。横軸が日数、縦軸がTIME_WAITの数です。49.7日以降でTIME_WAIT数が増加した様子はありますが、それ以前から増加していて、52日以降は0が多くなっていることがわかります。また45日辺りでも3程度が継続した時期もあります。ということで、49.7日辺りでTIME_WAITが少し増えたものの、Mac miniの利用状況によるもので、49.7日問題と関係づけることはできないと思われます。何よりも、単調増加する様子が見られません。なので、カウンターがオーバーフローしてTIME_WAIT状態のソケットが蓄積される状況は発生してい無い様子です。

timewait2.png

ということで、現状のMac miniの使い方をしている限り、49.7日問題は発生しませんでした。もう少し様子を見てみようかとは思います。

まとめ

macOSデフォルトのエネルギー設定(スリープ設定)で使う限り、Mac miniを連続稼働しても、49.7日問題は発生しませんでした。安心しました。

想像ですが、スリープ中はtcp_now変数のカウントアップが止まっているのかもしれません。だとすると稼働時間が100日や150日にくらいになったところで障害が発生するのかもしれません。また、tcp_now変数は再起動でリセットされるようなので、スリープ復帰でもリセットされている可能性もあります。だとすれば、障害が発生することはまず無いと考えられます。tcp_now変数にアクセスできれば、その辺りを確認できるかもしれません。


追記:Claudeさんに聞きながら色々試みたところ、「実機のApple Siliconではリリース版カーネル自体からシンボルが公開されていないため、tcp_nowを直接読み出すことは困難」なようでした。その代わり、「パケットのタイムスタンプオプションから間接的に観察する」方法で、tcp_nowのカウントアップ状況を確認できました。、スリープ中はtcp_now変数のカウントアップが滞っているようです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?