[インデックス 11070] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるEnviron
関数の挙動を修正し、環境変数の順序が元の順序と一致するように変更するものです。これにより、環境変数の取得順序に関するバグ(Issue 2619)が修正されます。
コミット
commit 024952fb8a75ca12e30cda9d9b52fb9ad653b6c4
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Mon Jan 9 16:51:20 2012 -0800
syscall: make Environ return original order
Fixes #2619
R=r, rsc
CC=golang-dev
https://golang.org/cl/5528058
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/024952fb8a75ca12e30cda9d9b52fb9ad653b6c4
元コミット内容
commit 024952fb8a75ca12e30cda9d9b52fb9ad653b6c4
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Mon Jan 9 16:51:20 2012 -0800
syscall: make Environ return original order
Fixes #2619
R=r, rsc
CC=golang-dev
https://golang.org/cl/5528058
---
src/pkg/os/env_test.go | 11 ++++++++\n src/pkg/syscall/env_unix.go | 64 +++++++++++++++++++++++++++++++--------------
2 files changed, 55 insertions(+), 20 deletions(-)
diff --git a/src/pkg/os/env_test.go b/src/pkg/os/env_test.go
index 04ff390727..991fa4d057 100644
--- a/src/pkg/os/env_test.go
+++ b/src/pkg/os/env_test.go
@@ -6,6 +6,7 @@ package os_test
import (
. "os"
+ "reflect"
"testing"
)
@@ -57,3 +58,13 @@ func TestExpand(t *testing.T) {
}
}
}\n+\n+func TestConsistentEnviron(t *testing.T) {\n+\te0 := Environ()\n+\tfor i := 0; i < 10; i++ {\n+\t\te1 := Environ()\n+\t\tif !reflect.DeepEqual(e0, e1) {\n+\t\t\tt.Fatalf(\"environment changed\")\n+\t\t}\n+\t}\n+}\ndiff --git a/src/pkg/syscall/env_unix.go b/src/pkg/syscall/env_unix.go
index 2c873cbbad..8b1868c271 100644
--- a/src/pkg/syscall/env_unix.go
+++ b/src/pkg/syscall/env_unix.go
@@ -10,26 +10,40 @@ package syscall
import "sync"
-var env map[string]string
-var envOnce sync.Once
-var envs []string // provided by runtime
+var (
+ // envOnce guards initialization by copyenv, which populates env.
+ envOnce sync.Once
+ // envLock guards env and envs.
+ envLock sync.RWMutex
+
+ // env maps from an environment variable to its first occurrence in envs.
+ env map[string]int
+
+ // envs is provided by the runtime. elements are expected to be
+ // of the form "key=value".
+ envs []string
+)
+
+// setenv_c is provided by the runtime, but is a no-op if cgo isn\'t
+// loaded.
func setenv_c(k, v string)
func copyenv() {
- env = make(map[string]string)
- for _, s := range envs {
+ env = make(map[string]int)
+ for i, s := range envs {
for j := 0; j < len(s); j++ {\n \t\t\tif s[j] == \'=\' {\n - \t\t\tenv[s[0:j]] = s[j+1:]
+\t\t\t\tkey := s[:j]
+\t\t\t\tif _, ok := env[key]; !ok {\n+\t\t\t\t\tenv[key] = i
+\t\t\t\t}\n \t\t\t\tbreak
\t\t\t}
\t\t}\n \t}\n }\n
-var envLock sync.RWMutex
-
func Getenv(key string) (value string, found bool) {
envOnce.Do(copyenv)
if len(key) == 0 {
@@ -39,11 +53,17 @@ func Getenv(key string) (value string, found bool) {
envLock.RLock()
defer envLock.RUnlock()
- v, ok := env[key]
+ i, ok := env[key]
if !ok {
return "", false
}
- return v, true
+\ts := envs[i]
+\tfor i := 0; i < len(s); i++ {\n+\t\tif s[i] == \'=\' {\n+\t\t\treturn s[i+1:], true
+\t\t}\n+\t}\n+\treturn "", false
}\n
func Setenv(key, value string) error {
envOnce.Do(copyenv)
@@ -55,8 +75,16 @@ func Setenv(key, value string) error {\n envLock.Lock()
defer envLock.Unlock()
- env[key] = value
- setenv_c(key, value) // is a no-op if cgo isn\'t loaded
+\ti, ok := env[key]
+\tkv := key + "=" + value
+\tif ok {\n+\t\tenvs[i] = kv
+\t} else {\n+\t\ti = len(envs)
+\t\tenvs = append(envs, kv)
+\t}\n+\tenv[key] = i
+\tsetenv_c(key, value)
return nil
}\n
@@ -66,8 +94,8 @@ func Clearenv() {
envLock.Lock()
defer envLock.Unlock()
- env = make(map[string]string)
-
+\tenv = make(map[string]int)
+\tenvs = []string{}\n // TODO(bradfitz): pass through to C
}\n
@@ -75,11 +103,7 @@ func Environ() []string {
envOnce.Do(copyenv)
envLock.RLock()
defer envLock.RUnlock()
- a := make([]string, len(env))
- i := 0
- for k, v := range env {
- a[i] = k + "=" + v
- i++
- }\n+\ta := make([]string, len(envs))\n+\tcopy(a, envs)\n return a
}\n
変更の背景
このコミットは、Go言語のsyscall
パッケージにおけるEnviron
関数の既存のバグ(Issue 2619)を修正するために行われました。以前の実装では、Environ
関数が環境変数を返す際に、その順序が保証されていませんでした。特に、map
を使用して環境変数を管理していたため、map
の性質上、要素の順序が不定となり、結果としてEnviron
が呼び出されるたびに異なる順序で環境変数が返される可能性がありました。
環境変数の順序は、一部のアプリケーションやシェルスクリプトにおいて重要な意味を持つ場合があります。例えば、PATH
のような環境変数は、その中に含まれるディレクトリの順序によってコマンドの解決順序が変わるため、順序が保証されないと予期せぬ動作を引き起こす可能性があります。このコミットは、このような順序依存の問題を解決し、Environ
関数が常に環境変数を元の順序で返すようにすることで、より予測可能で堅牢な動作を実現することを目的としています。
前提知識の解説
- 環境変数 (Environment Variables): オペレーティングシステムがプロセスに提供する動的な名前付きの値の集合です。プログラムの動作に影響を与える設定情報(例:
PATH
,HOME
,LANG
など)を格納するために使用されます。各環境変数は通常、「キー=値」の形式で表現されます。 syscall
パッケージ: Go言語の標準ライブラリの一部で、低レベルのオペレーティングシステムプリミティブへのアクセスを提供します。これには、ファイルシステム操作、プロセス管理、ネットワーク通信、そして環境変数へのアクセスなどが含まれます。syscall.Environ()
関数: 現在のプロセスの環境変数をすべて取得し、[]string
(文字列のスライス)として返します。各文字列は「キー=値」の形式です。map
(Go言語): キーと値のペアを格納するGo言語の組み込みデータ構造です。map
の重要な特性は、要素の順序が保証されないことです。要素をイテレートする際の順序は、Goのランタイムによって決定され、実行ごとに異なる場合があります。sync.Once
: Go言語のsync
パッケージにある型で、特定のコードブロックが一度だけ実行されることを保証するために使用されます。このコミットでは、環境変数の初期化処理が複数回行われないようにするために使用されています。sync.RWMutex
: 読み書きロック(Reader-Writer Mutex)を提供するGo言語のsync
パッケージにある型です。複数のゴルーチンが同時に読み取りアクセスすることを許可しますが、書き込みアクセスは一度に1つのゴルーチンのみに制限します。これにより、環境変数データへの並行アクセス時のデータ競合を防ぎます。
技術的詳細
このコミットの主要な変更点は、syscall
パッケージが環境変数を内部でどのように管理するかという点にあります。以前の実装では、環境変数はmap[string]string
型のenv
変数に格納されていました。しかし、map
は順序を保証しないため、Environ()
関数が呼び出されるたびに環境変数の順序が変動する可能性がありました。
この修正では、以下の変更が導入されました。
-
環境変数の格納方法の変更:
- 以前は
var env map[string]string
として環境変数のキーと値を直接マップしていましたが、これをvar env map[string]int
に変更しました。この新しいenv
マップは、環境変数のキーを、envs
スライス内のその環境変数のインデックスにマッピングします。 - 環境変数の実際の「キー=値」形式の文字列は、
var envs []string
というスライスに格納されるようになりました。このenvs
スライスは、Goランタイムによって提供される元の環境変数の順序を保持します。
- 以前は
-
copyenv()
関数の変更:copyenv()
関数は、envs
スライスをイテレートし、各環境変数のキーとそのenvs
スライス内のインデックスを新しいenv
マップに格納するように変更されました。これにより、Getenv
でキーから値を検索する際に、元の順序を保持したenvs
スライスから値を取得できるようになります。- 同じキーの環境変数が複数存在する場合(これは通常は起こりませんが、理論的にはありえます)、
env
マップには最初に出現した環境変数のインデックスが格納されます。
-
Getenv()
関数の変更:Getenv()
関数は、env
マップからキーに対応するenvs
スライス内のインデックスを取得し、そのインデックスを使用してenvs
スライスから実際の環境変数文字列を取得するように変更されました。これにより、Getenv
も元の順序を考慮した動作になります。
-
Setenv()
関数の変更:Setenv()
関数は、既存の環境変数を更新する場合、env
マップから既存のインデックスを取得し、そのインデックスのenvs
スライス内の要素を更新します。- 新しい環境変数を設定する場合、
envs
スライスの末尾に新しい環境変数を追加し、その新しいインデックスをenv
マップに格納します。これにより、新しい環境変数は追加された順序でenvs
スライスに保持されます。
-
Clearenv()
関数の変更:Clearenv()
関数は、env
マップをクリアするだけでなく、envs
スライスも空にするように変更されました。
-
Environ()
関数の変更:Environ()
関数は、env
マップをイテレートする代わりに、envs
スライスのコピーを直接返すように変更されました。これにより、Environ()
は常に環境変数がプロセスに渡された元の順序で返されることが保証されます。
-
テストの追加:
os/env_test.go
にTestConsistentEnviron
という新しいテストが追加されました。このテストは、Environ()
を複数回呼び出し、返される環境変数のスライスがreflect.DeepEqual
で比較して常に同じであることを確認することで、順序の一貫性を検証します。
これらの変更により、syscall.Environ()
は環境変数の元の順序を正確に反映するようになり、順序に依存するアプリケーションの互換性と信頼性が向上しました。
コアとなるコードの変更箇所
diff --git a/src/pkg/os/env_test.go b/src/pkg/os/env_test.go
index 04ff390727..991fa4d057 100644
--- a/src/pkg/os/env_test.go
+++ b/src/pkg/os/env_test.go
@@ -6,6 +6,7 @@ package os_test
import (
. "os"
+ "reflect"
"testing"
)
@@ -57,3 +58,13 @@ func TestExpand(t *testing.T) {
}
}
}\n+\n+func TestConsistentEnviron(t *testing.T) {\n+\te0 := Environ()\n+\tfor i := 0; i < 10; i++ {\n+\t\te1 := Environ()\n+\t\tif !reflect.DeepEqual(e0, e1) {\n+\t\t\tt.Fatalf("environment changed")\n+\t\t}\n+\t}\n+}\ndiff --git a/src/pkg/syscall/env_unix.go b/src/pkg/syscall/env_unix.go
index 2c873cbbad..8b1868c271 100644
--- a/src/pkg/syscall/env_unix.go
+++ b/src/pkg/syscall/env_unix.go
@@ -10,26 +10,40 @@ package syscall
import "sync"
-var env map[string]string
-var envOnce sync.Once
-var envs []string // provided by runtime
+var (
+ // envOnce guards initialization by copyenv, which populates env.
+ envOnce sync.Once
+ // envLock guards env and envs.
+ envLock sync.RWMutex
+
+ // env maps from an environment variable to its first occurrence in envs.
+ env map[string]int
+
+ // envs is provided by the runtime. elements are expected to be
+ // of the form "key=value".
+ envs []string
+)
+
+// setenv_c is provided by the runtime, but is a no-op if cgo isn\'t
+// loaded.
func setenv_c(k, v string)
func copyenv() {
- env = make(map[string]string)
- for _, s := range envs {
+ env = make(map[string]int)
+ for i, s := range envs {
for j := 0; j < len(s); j++ {\n \t\t\tif s[j] == \'=\' {\n - \t\t\tenv[s[0:j]] = s[j+1:]
+\t\t\t\tkey := s[:j]
+\t\t\t\tif _, ok := env[key]; !ok {\n+\t\t\t\t\tenv[key] = i
+\t\t\t\t}\n \t\t\t\tbreak
\t\t\t}
\t\t}\n \t}\n }\n
-var envLock sync.RWMutex
-
func Getenv(key string) (value string, found bool) {
envOnce.Do(copyenv)
if len(key) == 0 {
@@ -39,11 +53,17 @@ func Getenv(key string) (value string, found bool) {
envLock.RLock()
defer envLock.RUnlock()
- v, ok := env[key]
+ i, ok := env[key]
if !ok {
return "", false
}
- return v, true
+\ts := envs[i]
+\tfor i := 0; i < len(s); i++ {\n+\t\tif s[i] == \'=\' {\n+\t\t\treturn s[i+1:], true
+\t\t}\n+\t}\n+\treturn "", false
}\n
func Setenv(key, value string) error {
envOnce.Do(copyenv)
@@ -55,8 +75,16 @@ func Setenv(key, value string) error {\n envLock.Lock()
defer envLock.Unlock()
- env[key] = value
- setenv_c(key, value) // is a no-op if cgo isn\'t loaded
+\ti, ok := env[key]
+\tkv := key + "=" + value
+\tif ok {\n+\t\tenvs[i] = kv
+\t} else {\n+\t\ti = len(envs)
+\t\tenvs = append(envs, kv)
+\t}\n+\tenv[key] = i
+\tsetenv_c(key, value)
return nil
}\n
@@ -66,8 +94,8 @@ func Clearenv() {
envLock.Lock()
defer envLock.Unlock()
- env = make(map[string]string)
-
+\tenv = make(map[string]int)
+\tenvs = []string{}\n // TODO(bradfitz): pass through to C
}\n
@@ -75,11 +103,7 @@ func Environ() []string {
envOnce.Do(copyenv)
envLock.RLock()
defer envLock.RUnlock()
- a := make([]string, len(env))
- i := 0
- for k, v := range env {
- a[i] = k + "=" + v
- i++
- }\n+\ta := make([]string, len(envs))\n+\tcopy(a, envs)\n return a
}\n```
## コアとなるコードの解説
このコミットにおける主要なコード変更は、`src/pkg/syscall/env_unix.go`ファイルに集中しています。
1. **変数定義の変更**:
* 以前は`env map[string]string`と`envs []string`が別々に定義されていましたが、新しいコードでは`env`が`map[string]int`型に変更され、`envs`スライスはそのまま残されています。
* `env`マップは、環境変数名(キー)から`envs`スライス内の対応する環境変数のインデックスへのマッピングを保持するようになりました。これにより、環境変数の検索を効率的に行いつつ、`envs`スライスで元の順序を維持できます。
* `envLock`(読み書きミューテックス)は、`env`と`envs`の両方を保護するように明示的にコメントが追加されました。
2. **`copyenv()`関数の変更**:
* この関数は、環境変数を初期化する際に呼び出されます。
* 変更前は、`envs`スライスから直接`env`マップにキーと値をコピーしていました。
* 変更後では、`envs`スライスをイテレートし、各環境変数のキーと、その環境変数が`envs`スライス内で最初に出現するインデックスを`env`マップに格納します。これにより、`env`マップは検索用インデックスとして機能し、実際の値は順序が保証された`envs`スライスから取得されるようになります。
3. **`Getenv()`関数の変更**:
* 環境変数の値を取得する関数です。
* 変更前は、`env`マップから直接値を取得していました。
* 変更後では、`env`マップからキーに対応する`envs`スライス内のインデックス`i`を取得します。その後、そのインデックス`i`を使用して`envs[i]`から環境変数文字列全体を取得し、そこから値の部分を抽出して返します。これにより、`Getenv`も`envs`スライスの順序を間接的に利用する形になります。
4. **`Setenv()`関数の変更**:
* 環境変数を設定する関数です。
* 変更前は、単に`env`マップに新しいキーと値を設定していました。
* 変更後では、設定しようとしているキーが既に`env`マップに存在するかどうかを確認します。
* もし存在すれば、そのキーに対応する`envs`スライス内の既存の要素を新しい「キー=値」の文字列で更新します。
* 存在しない場合は、新しい「キー=値」の文字列を`envs`スライスの末尾に追加し、その新しいインデックスを`env`マップに格納します。これにより、環境変数の追加順序が`envs`スライスに反映されます。
5. **`Clearenv()`関数の変更**:
* すべての環境変数をクリアする関数です。
* 変更前は`env`マップのみをクリアしていました。
* 変更後では、`env`マップをクリアするとともに、`envs`スライスも空の新しいスライスに置き換えることで、環境変数の状態を完全にリセットします。
6. **`Environ()`関数の変更**:
* すべての環境変数を文字列のスライスとして返す関数です。
* 変更前は、`env`マップをイテレートして新しいスライスを構築していました。この方法では`map`の順序不定性により、返されるスライスの順序も不定でした。
* 変更後では、`envs`スライスのコピーを直接返します。`envs`スライスは元の環境変数の順序を保持しているため、`Environ()`は常に元の順序で環境変数を返すことが保証されます。
7. **テストファイルの変更 (`src/pkg/os/env_test.go`)**:
* `reflect`パッケージがインポートされました。これは、Goのデータ構造(この場合は文字列のスライス)のディープ比較を行うために使用されます。
* `TestConsistentEnviron`という新しいテスト関数が追加されました。このテストは、`Environ()`関数を複数回呼び出し、それぞれの呼び出しで返される環境変数のスライスが`reflect.DeepEqual`によって完全に一致することを確認します。これにより、`Environ()`が常に一貫した順序で環境変数を返すことが保証されます。
これらの変更により、Goの環境変数管理は、順序の保証という重要な特性を獲得し、より堅牢で予測可能な動作を提供するようになりました。
## 関連リンク
* Go Issue 2619: [https://github.com/golang/go/issues/2619](https://github.com/golang/go/issues/2619)
* Go CL 5528058: [https://golang.org/cl/5528058](https://golang.org/cl/5528058)
## 参考にした情報源リンク
* [https://github.com/golang/go/commit/024952fb8a75ca12e30cda9d9b52fb9ad653b6c4](https://github.com/golang/go/commit/024952fb8a75ca12e30cda9d9b52fb9ad653b6c4)
* Go言語のドキュメント (syscall, os, syncパッケージ)
* Go言語の`map`の特性に関する一般的な知識