[インデックス 13000] ファイルの概要
このコミットは、Go言語のダッシュボードプロジェクトにおけるコードレビューシステムの一部である、メール送信機能とデータストアへのエンティティ保存に関するバグ修正と改善を目的としています。具体的には、メールの即時送信と、time.Time
型フィールドのゼロ値がデータストアに保存される際に発生する問題を解決しています。
コミット
commit dae2992c98bff181b023f27889c513d89714f5ac
Author: David Symonds <dsymonds@golang.org>
Date: Tue May 1 11:33:25 2012 +1000
misc/dashboard/codereview: send mail immediately, and fill in time.Time fields.
If we delay the mail sending, we can't send as the current user.
If we don't fill in the time.Time fields, datastore.Put will fail
because the zero time.Time value is out of its range.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6136053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dae2992c98bff181b023f27889c513d89714f5ac
元コミット内容
misc/dashboard/codereview: send mail immediately, and fill in time.Time fields.
If we delay the mail sending, we can't send as the current user.
If we don't fill in the time.Time fields, datastore.Put will fail
because the zero time.Time value is out of its range.
変更の背景
このコミットには主に2つの背景があります。
-
メール送信のユーザーコンテキスト維持: 以前の実装では、メール送信に
appengine/delay
パッケージを使用していました。これは、タスクキューを介して非同期に処理を遅延実行するための機能です。しかし、メール送信を遅延させると、メールが送信される時点でのユーザーコンテキスト(つまり、メールを送信したユーザーの認証情報)が失われるという問題がありました。これにより、メールが「現在のユーザーとして」送信されず、システムからのメールとして扱われるか、あるいは送信自体が失敗する可能性がありました。コードレビューシステムでは、特定のユーザーがアクションを起こした結果としてメールが送信されるため、そのユーザーのコンテキストでメールが送られることが重要でした。 -
time.Time
型のゼロ値とDatastoreの制約: Google App EngineのDatastore(データストア)は、特定のデータ型に対して制約を持っています。time.Time
型の場合、Go言語におけるtime.Time
のゼロ値(time.Time{}
、つまり0001-01-01 00:00:00 +0000 UTC
)は、Datastoreが許容する日付/時刻の範囲外となることがあります。特に、Datastoreは通常、紀元1年より前の日付をサポートしていません。そのため、新しいエンティティをDatastoreに保存する際に、Created
やModified
といったtime.Time
型のフィールドが明示的に初期化されていないと、それらがゼロ値となり、datastore.Put
呼び出しが失敗していました。
これらの問題を解決するため、メールは即時送信されるように変更され、新しいエンティティがDatastoreに保存される際にはtime.Time
型のフィールドに適切な初期値(Unixエポックのゼロタイム)が設定されるようになりました。
前提知識の解説
このコミットを理解するためには、以下のGoogle App Engine (GAE) およびGo言語の概念に関する知識が必要です。
- Google App Engine (GAE): Googleが提供するPaaS (Platform as a Service) で、ウェブアプリケーションやモバイルバックエンドを構築・ホストするためのプラットフォームです。Go言語を含む複数の言語をサポートしています。
appengine
パッケージ: GAE上でGoアプリケーションを開発するための基本的なAPIを提供します。アプリケーションのコンテキスト管理、ログ出力、エラーハンドリングなどに使用されます。appengine/datastore
パッケージ: GAEのNoSQLデータベースであるDatastoreを操作するためのAPIを提供します。エンティティの保存 (Put
)、取得 (Get
)、クエリ (Query
) などを行います。datastore.Put(c, key, src)
: 指定されたエンティティをDatastoreに保存します。c
はコンテキスト、key
はエンティティのキー、src
は保存するGoの構造体です。datastore.ErrNoSuchEntity
:datastore.Get
などの操作で、指定されたキーに対応するエンティティが見つからなかった場合に返されるエラーです。
appengine/mail
パッケージ: GAEアプリケーションからメールを送信するためのAPIを提供します。mail.Send(c, msg)
: 指定されたメールメッセージを送信します。
appengine/delay
パッケージ: GAEのタスクキューを利用して、関数の実行を非同期に遅延させるためのAPIを提供します。これにより、HTTPリクエストの処理中に時間のかかる操作を実行し、レスポンスをすぐに返すことができます。delay.Func(name, f)
: 遅延実行可能な関数を定義します。Func.Call(c, args...)
: 遅延実行可能な関数をタスクキューに追加し、非同期に実行をスケジュールします。
appengine/taskqueue
パッケージ: GAEのタスクキューを直接操作するためのAPIを提供します。appengine/delay
は、このtaskqueue
パッケージの上に構築された高レベルな抽象化です。appengine/urlfetch
パッケージ: GAEアプリケーションからHTTPリクエストを送信するためのAPIを提供します。time.Time
型 (Go言語): Go言語の標準ライブラリtime
パッケージで提供される、日付と時刻を表す構造体です。- ゼロ値: Goの構造体は、明示的に初期化されない場合、そのフィールドはそれぞれの型のゼロ値で初期化されます。
time.Time
型のゼロ値は、time.Time{}
で表現され、通常は0001-01-01 00:00:00 +0000 UTC
となります。 time.Unix(sec, nsec)
: Unixエポック(1970年1月1日00:00:00 UTC)からの秒数とナノ秒数に基づいてtime.Time
値を生成します。time.Unix(0, 0)
はUnixエポックのゼロタイムを表します。
- ゼロ値: Goの構造体は、明示的に初期化されない場合、そのフィールドはそれぞれの型のゼロ値で初期化されます。
技術的詳細
このコミットの技術的詳細は、Google App Engineの特定の挙動とGo言語の型システムに深く関連しています。
-
appengine/delay
とユーザーコンテキストの喪失:appengine/delay
パッケージは、内部的にGAEのタスクキューを利用して関数の実行をスケジュールします。HTTPリクエストのコンテキスト(appengine.Context
)は、リクエストのライフサイクルに紐付けられています。delay.Func.Call
で関数を遅延実行すると、その関数は元のHTTPリクエストとは異なる新しいタスクとして、後で実行されます。この新しいタスクは、元のリクエストのユーザー認証情報やその他のコンテキスト情報を自動的に引き継ぎません。 特にメール送信の場合、mail.Send
関数は、現在のユーザーの認証情報を使用してメールを送信しようとします。しかし、遅延実行されたタスクでは「現在のユーザー」が存在しないか、あるいはシステムアカウントのような別のユーザーとして実行されるため、メールが意図した送信者として送られない、または送信権限の問題で失敗する可能性がありました。このコミットでは、メール送信を即時実行することで、元のHTTPリクエストのコンテキスト内でmail.Send
が呼び出され、適切なユーザーとしてメールが送信されるように修正しています。 -
time.Time
のゼロ値とDatastoreの範囲外エラー: Go言語のtime.Time
型のゼロ値は、0001-01-01 00:00:00 +0000 UTC
です。Google App EngineのDatastoreは、日付/時刻型のプロパティに対して、通常は紀元1年より前の日付をサポートしていません。Datastoreの内部表現やインデックスの制約により、この「ゼロ年」に近い日付は無効な値として扱われることがあります。 新しいエンティティをDatastoreに保存する際、もしCreated
やModified
のようなtime.Time
型のフィールドがGoの構造体で宣言されているにもかかわらず、コード内で明示的に初期化されていない場合、それらは自動的にGoのゼロ値(0001-01-01...
)で初期化されます。このゼロ値を持つエンティティをdatastore.Put
で保存しようとすると、Datastoreがその日付を無効と判断し、保存操作が失敗します。 このコミットでは、datastore.ErrNoSuchEntity
(つまり、新しいエンティティである場合)のチェックを追加し、その場合にCreated
とModified
フィールドをtime.Unix(0, 0)
(Unixエポックのゼロタイム、1970-01-01 00:00:00 +0000 UTC
)で明示的に初期化することで、Datastoreが許容する有効な日付範囲内に収まるようにしています。これにより、datastore.Put
の失敗を防ぎます。
コアとなるコードの変更箇所
このコミットでは、以下の2つのファイルが変更されています。
-
misc/dashboard/codereview/dashboard/cl.go
:appengine/delay
パッケージのインポートが削除されました。sendMailLater
というdelay.Func
で定義された変数が削除されました。handleAssign
関数内で、sendMailLater.Call(c, msg)
という遅延メール送信の呼び出しが、直接mail.Send(c, msg)
に置き換えられました。mail.Send
の呼び出しにエラーハンドリングが追加され、エラーが発生した場合はコンテキストにエラーログが出力されるようになりました。
-
misc/dashboard/codereview/dashboard/mail.go
:handleMail
関数内で、datastore.ErrNoSuchEntity
(エンティティが存在しない、つまり新規作成の場合)のチェックが追加されました。- もしエンティティが新規作成の場合、
cl.Created
とcl.Modified
フィールドがtime.Unix(0, 0)
で明示的に初期化されるようになりました。
コアとなるコードの解説
misc/dashboard/codereview/dashboard/cl.go
の変更
--- a/misc/dashboard/codereview/dashboard/cl.go
+++ b/misc/dashboard/codereview/dashboard/cl.go
@@ -16,7 +16,6 @@ import (
"appengine"
"appengine/datastore"
- "appengine/delay"
"appengine/mail"
"appengine/taskqueue"
"appengine/urlfetch"
@@ -105,8 +104,6 @@ func (cl *CL) ModifiedAgo() string {
return "just now"
}
-var sendMailLater = delay.Func("send-mail", mail.Send)
-
func handleAssign(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
@@ -196,7 +193,9 @@ func handleAssign(w http.ResponseWriter, r *http.Request) {
}
// TODO(dsymonds): Use cl.LastMessageID as the In-Reply-To header
// when the appengine/mail package supports that.
- sendMailLater.Call(c, msg)
+ if err := mail.Send(c, msg); err != nil {
+ c.Errorf("mail.Send: %v", err)
+ }
}
}
}
-
import "appengine/delay"
の削除:appengine/delay
パッケージは、メール送信を非同期に遅延させるために使用されていました。メール送信を即時実行する方針に変更されたため、このパッケージは不要となり削除されました。これにより、コードの依存関係が減少し、シンプルになります。 -
var sendMailLater = delay.Func("send-mail", mail.Send)
の削除:sendMailLater
変数は、mail.Send
関数をappengine/delay
のメカニズムでラップし、遅延実行可能にするためのものでした。遅延実行が不要になったため、この変数も削除されました。 -
sendMailLater.Call(c, msg)
からmail.Send(c, msg)
への変更: これがメール送信ロジックの核心的な変更です。以前はdelay.Func
を介してメール送信がタスクキューに登録され、非同期に実行されていました。この変更により、mail.Send
関数がhandleAssign
関数内で直接呼び出されるようになりました。これにより、メール送信はHTTPリクエストの処理中に同期的に行われ、リクエストをトリガーしたユーザーのコンテキストが維持されたままメールが送信されるようになります。 -
エラーハンドリングの追加:
if err := mail.Send(c, msg); err != nil { c.Errorf("mail.Send: %v", err) }
直接mail.Send
を呼び出すようになったため、その戻り値であるエラーを適切に処理する必要があります。ここでは、エラーが発生した場合にappengine.Context
のErrorf
メソッドを使用してログに出力しています。これにより、メール送信の失敗を監視し、デバッグすることが可能になります。
misc/dashboard/codereview/dashboard/mail.go
の変更
--- a/misc/dashboard/codereview/dashboard/mail.go
+++ b/misc/dashboard/codereview/dashboard/mail.go
@@ -45,6 +45,12 @@ func handleMail(w http.ResponseWriter, r *http.Request) {
if err != nil && err != datastore.ErrNoSuchEntity {
return err
}
+ if err == datastore.ErrNoSuchEntity {
+ // Must set sentinel values for time.Time fields
+ // if this is a new entity.
+ cl.Created = time.Unix(0, 0)
+ cl.Modified = time.Unix(0, 0)
+ }
cl.LastMessageID = msg.Header.Get("Message-ID")
_, err = datastore.Put(c, key, cl)
return err
-
if err == datastore.ErrNoSuchEntity
の追加:handleMail
関数は、おそらく受信したメールメッセージを処理し、それに関連するコードレビュー(cl
エンティティ)をDatastoreに保存する役割を担っています。datastore.Get
などの操作でdatastore.ErrNoSuchEntity
が返されるのは、対応するエンティティがDatastoreにまだ存在しない、つまり新しいエンティティを作成する必要がある場合です。 -
cl.Created = time.Unix(0, 0)
とcl.Modified = time.Unix(0, 0)
の初期化: 新しいエンティティの場合、cl.Created
とcl.Modified
というtime.Time
型のフィールドは、Goのデフォルトのゼロ値(0001-01-01...
)で初期化されてしまいます。前述の通り、このゼロ値はDatastoreが許容する日付範囲外であるため、datastore.Put
が失敗する原因となります。 この変更では、time.Unix(0, 0)
を使用して、これらのフィールドをUnixエポックのゼロタイム(1970-01-01 00:00:00 +0000 UTC
)で明示的に初期化しています。この値はDatastoreが有効な日付として認識するため、datastore.Put
が正常に実行されるようになります。これにより、新しいエンティティの保存時の堅牢性が向上します。
関連リンク
参考にした情報源リンク
- Google App Engine Documentation (Go)
- Go Language
time
package documentation - Go Language
appengine/datastore
package documentation - Go Language
appengine/mail
package documentation - Go Language
appengine/delay
package documentation