[インデックス 10334] ファイルの概要
このコミットは、GoランタイムにおけるOpenBSDプラットフォームでのsemasleep()
関数の挙動を修正するものです。具体的には、thrsleep()
システムコールに渡されるtimespec
構造体の値が、絶対時間として正しく解釈されるように、現在のナノ秒時間を加算する変更が加えられました。
コミット
- コミットハッシュ:
85b7419211d9d46cc7a73c3f8595f2a3e9ca15ff
- 作者: Joel Sing jsing@google.com
- コミット日時: 2011年11月10日 木曜日 11:42:01 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/85b7419211d9d46cc7a73c3f8595f2a3e9ca15ff
元コミット内容
runtime: fix semasleep() duration for openbsd
The timespec passed to thrsleep() needs to be an absolute/realtime
value, so add the current nanotime to ns.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5374048
変更の背景
Goランタイムは、ゴルーチン(goroutine)のスケジューリングや同期のために、オペレーティングシステム(OS)の提供する低レベルなスリープおよび同期プリミティブを利用します。OpenBSD環境において、Goランタイムはthrsleep()
というシステムコールを使用してゴルーチンをスリープさせたり、セマフォなどの同期イベントを待機させたりします。
thrsleep()
システムコールは、スリープ期間を指定するためにtimespec
構造体を受け取ります。このtimespec
構造体の解釈はOSによって異なり、OpenBSDの場合、CLOCK_REALTIME
を指定してthrsleep()
を呼び出す際には、渡されるtimespec
の値が「絶対時間」である必要があります。つまり、特定のエポック(通常は1970年1月1日UTC)からの経過時間を表す必要があります。
しかし、元の実装では、semasleep()
関数内で計算されたスリープ期間(ns
、ナノ秒単位)が、単に相対的な期間としてtimespec
に変換されていました。この相対的な期間をそのまま絶対時間を期待するthrsleep()
に渡すと、意図しないスリープ挙動や、場合によっては無限スリープのような問題が発生する可能性がありました。このコミットは、このOpenBSD特有のthrsleep()
の要件を満たすために、timespec
に設定する前に現在の絶対時間を加算することで、この問題を解決することを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念を把握しておく必要があります。
-
semasleep()
: Goランタイム内部で使用される関数で、セマフォ(semaphore)の待機処理に関連します。ゴルーチンがリソースの解放を待つ際に、この関数を通じてOSレベルのスリープメカニズムが利用されます。Goの同期プリミティブ(例:sync.Mutex
)の基盤となる実装の一部です。 -
thrsleep()
: OpenBSDに存在する低レベルなシステムコールです。スレッドを特定の条件が満たされるまで、または指定された時間までスリープさせるために使用されます。このシステムコールは、timespec
構造体とクロックID(例:CLOCK_REALTIME
)を受け取り、スリープの挙動を制御します。 -
timespec
構造体: POSIX標準で定義されている時間値を表現するための構造体です。通常、tv_sec
(秒)とtv_nsec
(ナノ秒)の2つのフィールドを持ちます。システムコール(例:thrsleep()
,clock_gettime()
)は、この構造体を使用して時間情報をやり取りします。 -
CLOCK_REALTIME
: OpenBSDを含む多くのUNIX系OSで利用可能なクロックIDの一つです。これは協定世界時(UTC)の壁時計(wall-clock)を表し、1970年1月1日00:00:00 UTC(エポック)からの経過時間を提供します。CLOCK_REALTIME
はシステム管理者が調整できるため、不連続にジャンプする可能性があります。 -
絶対時間(Absolute Time) vs. 相対時間(Relative Time):
- 絶対時間: 特定の基準点(例: エポック)からの経過時間を表します。例えば、「2025年7月6日10時30分までスリープする」といった指定方法です。
- 相対時間: 現在の時点からの期間を表します。例えば、「今から5秒間スリープする」といった指定方法です。
thrsleep()
のようなシステムコールは、どちらの形式の時間指定を期待するかが重要であり、OpenBSDのCLOCK_REALTIME
を使用する場合は絶対時間が必要です。
-
runtime·nanotime()
: Goランタイム内部で現在の高精度なナノ秒時間を取得するために使用される関数です。これは通常、システム起動時からの経過時間(モノトニック時間)をナノ秒単位で返しますが、このコンテキストではCLOCK_REALTIME
と組み合わせて絶対時間を構築するために利用されます。
技術的詳細
このコミットの核心は、OpenBSDのthrsleep()
システムコールがCLOCK_REALTIME
クロックIDと共に使用される際に、timespec
構造体に絶対時間値を要求するという点にあります。
元のコードでは、semasleep()
関数内で計算されたスリープ期間ns
(ナノ秒)が、直接timespec
構造体のtv_sec
とtv_nsec
に変換されていました。
// 変更前
ts.tv_sec = ns/1000000000LL;
ts.tv_nsec = ns%1000000000LL;
runtime·thrsleep(&m->waitsemacount, CLOCK_REALTIME, &ts, &m->waitsemalock);
ここでns
が相対的なスリープ期間(例: 100ミリ秒)を表している場合、timespec
にはエポックからの絶対時刻ではなく、単に100ミリ秒という期間が設定されてしまいます。しかし、thrsleep()
がCLOCK_REALTIME
と共に呼び出される場合、ts
は「いつまでスリープするか」という絶対的な未来の時刻を示す必要があります。
この問題を解決するために、コミットではns
に現在の絶対ナノ秒時間(runtime·nanotime()
によって取得される)を加算する変更が加えられました。
// 変更後
ns += runtime·nanotime(); // ここで現在の絶対時間を加算
ts.tv_sec = ns/1000000000LL;
ts.tv_nsec = ns%1000000000LL;
runtime·thrsleep(&m->waitsemacount, CLOCK_REALTIME, &ts, &m->waitsemalock);
これにより、ns
は「現在の絶対時間 + 相対的なスリープ期間」という形式になり、結果としてtimespec
構造体には「未来の絶対時刻」が正しく設定されるようになります。thrsleep()
はこの絶対時刻までスレッドをスリープさせることができ、semasleep()
の意図通りの挙動がOpenBSD上で実現されます。
この修正は、GoランタイムがOSの低レベルなAPIと正しく連携し、プラットフォーム固有のセマンティクスを尊重することの重要性を示しています。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/openbsd/thread.c
ファイルの一箇所のみです。
--- a/src/pkg/runtime/openbsd/thread.c
+++ b/src/pkg/runtime/openbsd/thread.c
@@ -79,6 +79,7 @@ runtime·semasleep(int64 ns)
if(ns < 0)
runtime·thrsleep(&m->waitsemacount, 0, nil, &m->waitsemalock);
else {
+ ns += runtime·nanotime();
ts.tv_sec = ns/1000000000LL;
ts.tv_nsec = ns%1000000000LL;
runtime·thrsleep(&m->waitsemacount, CLOCK_REALTIME, &ts, &m->waitsemalock);
コアとなるコードの解説
変更された行は以下の1行です。
ns += runtime·nanotime();
この行は、semasleep
関数内でスリープ期間ns
が正の値(つまり、有限のスリープ期間が指定されている場合)であるときに実行されます。
ns
:semasleep
関数に渡された、スリープすべきナノ秒単位の期間です。これは相対的な期間として解釈されます。runtime·nanotime()
: Goランタイムが提供する関数で、現在の高精度なナノ秒時間を返します。OpenBSDのコンテキストでは、これは通常、CLOCK_REALTIME
に基づいた絶対時間に近い値を提供します。
この加算により、ns
の値は「現在の絶対時間」に「スリープすべき相対期間」が加算されたものになります。結果として得られるns
は、未来の特定の絶対時刻(エポックからのナノ秒数)を表すことになります。
この修正されたns
の値が、その後の2行でtimespec
構造体のtv_sec
(秒)とtv_nsec
(ナノ秒)に変換されます。
ts.tv_sec = ns/1000000000LL;
ts.tv_nsec = ns%1000000000LL;
そして、この絶対時刻を表すtimespec
構造体がruntime·thrsleep()
システムコールに渡されます。
runtime·thrsleep(&m->waitsemacount, CLOCK_REALTIME, &ts, &m->waitsemalock);
CLOCK_REALTIME
を指定してthrsleep()
を呼び出す場合、ts
は絶対時刻Error flushing log events: Error: getaddrinfo ENOTFOUND play.googleapis.com
at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:120:26) {
errno: -3008,
code: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'play.googleapis.com'
}
Error flushing log events: Error: getaddrinfo ENOTFOUND play.googleapis.com
at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:120:26) {
errno: -3008,
code: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'play.googleapis.com'
}
として解釈されるため、この修正によってOpenBSD上でのsemasleep()
の挙動が期待通りになり、ゴルーチンが正確な期間スリープするようになります。
関連リンク
- Go Code Review: https://golang.org/cl/5374048
参考にした情報源リンク
- OpenBSD
thrsleep
man page: https://man.openbsd.org/thrsleep.2 - OpenBSD
clock_gettime
man page: https://man.openbsd.org/clock_gettime.2 - Go runtime source code (general understanding of
semasleep
andnanotime
): (Web search results pointed to various Go source code snippets and discussions onsemasleep
andnanotime
implementations across different OSes.) - Stack Overflow discussions on
CLOCK_REALTIME
vsCLOCK_MONOTONIC
and absolute/relative time intimespec
: (General knowledge from web searches on these topics.) - Various articles and discussions on Go's runtime and OS interactions.