Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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に設定する前に現在の絶対時間を加算することで、この問題を解決することを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念を把握しておく必要があります。

  1. semasleep(): Goランタイム内部で使用される関数で、セマフォ(semaphore)の待機処理に関連します。ゴルーチンがリソースの解放を待つ際に、この関数を通じてOSレベルのスリープメカニズムが利用されます。Goの同期プリミティブ(例: sync.Mutex)の基盤となる実装の一部です。

  2. thrsleep(): OpenBSDに存在する低レベルなシステムコールです。スレッドを特定の条件が満たされるまで、または指定された時間までスリープさせるために使用されます。このシステムコールは、timespec構造体とクロックID(例: CLOCK_REALTIME)を受け取り、スリープの挙動を制御します。

  3. timespec構造体: POSIX標準で定義されている時間値を表現するための構造体です。通常、tv_sec(秒)とtv_nsec(ナノ秒)の2つのフィールドを持ちます。システムコール(例: thrsleep(), clock_gettime())は、この構造体を使用して時間情報をやり取りします。

  4. CLOCK_REALTIME: OpenBSDを含む多くのUNIX系OSで利用可能なクロックIDの一つです。これは協定世界時(UTC)の壁時計(wall-clock)を表し、1970年1月1日00:00:00 UTC(エポック)からの経過時間を提供します。CLOCK_REALTIMEはシステム管理者が調整できるため、不連続にジャンプする可能性があります。

  5. 絶対時間(Absolute Time) vs. 相対時間(Relative Time):

    • 絶対時間: 特定の基準点(例: エポック)からの経過時間を表します。例えば、「2025年7月6日10時30分までスリープする」といった指定方法です。
    • 相対時間: 現在の時点からの期間を表します。例えば、「今から5秒間スリープする」といった指定方法です。 thrsleep()のようなシステムコールは、どちらの形式の時間指定を期待するかが重要であり、OpenBSDのCLOCK_REALTIMEを使用する場合は絶対時間が必要です。
  6. runtime·nanotime(): Goランタイム内部で現在の高精度なナノ秒時間を取得するために使用される関数です。これは通常、システム起動時からの経過時間(モノトニック時間)をナノ秒単位で返しますが、このコンテキストではCLOCK_REALTIMEと組み合わせて絶対時間を構築するために利用されます。

技術的詳細

このコミットの核心は、OpenBSDのthrsleep()システムコールがCLOCK_REALTIMEクロックIDと共に使用される際に、timespec構造体に絶対時間値を要求するという点にあります。

元のコードでは、semasleep()関数内で計算されたスリープ期間ns(ナノ秒)が、直接timespec構造体のtv_sectv_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()の挙動が期待通りになり、ゴルーチンが正確な期間スリープするようになります。

関連リンク

参考にした情報源リンク

  • 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 and nanotime): (Web search results pointed to various Go source code snippets and discussions on semasleep and nanotime implementations across different OSes.)
  • Stack Overflow discussions on CLOCK_REALTIME vs CLOCK_MONOTONIC and absolute/relative time in timespec: (General knowledge from web searches on these topics.)
  • Various articles and discussions on Go's runtime and OS interactions.