Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Admin API purge/invalidation still panics with nil pointer on v0.16.0 (same root cause as #140)

オープン
#143 コメント 2 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
76/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
go
領域
api, backend

調査の方向性

admin.go の adminAPI.Provision と handleAPIEndpoints から始め、各ルートの SouinCaddyMiddleware が app.SurrogateStorage に値を設定する流れを追ってください。Caddy/FrankenPHP をコールドスタートし、admin purge または group-invalidation リクエストを実行して再現します。それらのリクエストが nil-pointer panic なしで完了すれば完了です。

索引モデルが issue の本文から書いたものです。

説明

cache-handler version(s) affected: v0.16.0

Description

Every call to the Souin admin API (purge, group-invalidation, etc.) panics with a nil pointer dereference — confirmed on a real deployment (FrankenPHP + github.com/caddyserver/cache-handler), not just in isolation. This looks like the same root cause as #140, still reproducing on v0.16.0.

Root cause

adminAPI.Provision() (in admin.go) resolves a.app (the *SouinApp instance) and returns. The handler map (InternalEndpointHandlers), which is built from a.app.Storers and a.app.SurrogateStorage, is constructed once, also inside Provision().

The problem: a.app.SurrogateStorage is populated by the per-route SouinCaddyMiddleware's own Provision() — a separate module, provisioned in an order Caddy doesn't guarantee relative to adminAPI.Provision(). When adminAPI.Provision() runs first (or the middleware simply hasn't provisioned yet for whatever route ordering reason), the handler map gets built with a nil SurrogateStorage baked in — permanently, since it's only ever built once, at Provision() time. Every subsequent admin API call then panics trying to use it.

How to reproduce

  1. Configure cache-handler with Cache-Tags/Surrogate-Key invalidation enabled (a purger hitting the Souin admin API).
  2. Start Caddy/FrankenPHP fresh (cold start matters — the provisioning-order race is timing-dependent, not always reproducible on every boot).
  3. Send any purge/invalidation request to the admin API (/souin-api/souin, PURGE method or group-invalidation endpoint).
  4. Nil pointer panic in the admin API handler, request fails.

Possible Solution

Defer building InternalEndpointHandlers until the first actual admin API request instead of at Provision() time, guarded by a sync.Once. By the time any real HTTP request reaches the admin API, both modules are guaranteed fully provisioned, so a.app.SurrogateStorage is populated correctly.

type adminAPI struct {
	ctx                      caddy.Context
	logger                   core.Logger
	app                      *SouinApp
	InternalEndpointHandlers *api.MapHandler
	handlersOnce             sync.Once
}

// ensureHandlers builds InternalEndpointHandlers on first use rather than at
// Provision time. adminAPI.Provision() and the per-route SouinCaddyMiddleware's
// Provision() (which populates app.SurrogateStorage) run in an unspecified
// order — building the handler map here at Provision time can capture a nil
// SurrogateStorage forever, crashing every purge call. Deferring the build
// until the first actual admin API request guarantees the app is fully
// provisioned by then.
func (a *adminAPI) ensureHandlers() {
	a.handlersOnce.Do(func() {
		config := Configuration{
			API: a.app.API,
			DefaultCache: DefaultCache{
				TTL: configurationtypes.Duration{
					Duration: 120 * time.Second,
				},
			},
		}
		a.InternalEndpointHandlers = api.GenerateHandlerMap(&config, a.app.Storers, a.app.SurrogateStorage)
	})
}

func (a *adminAPI) handleAPIEndpoints(writer http.ResponseWriter, request *http.Request) error {
	a.ensureHandlers()

	if a.InternalEndpointHandlers != nil {
		for k, handler := range *a.InternalEndpointHandlers.Handlers {
			if strings.Contains(request.RequestURI, k) {
				handler(writer, request)
				return nil
			}
		}
	}

	return caddy.APIError{
		HTTPStatus: http.StatusNotFound,
		Err:        fmt.Errorf("resource not found: %v", request.URL.Path),
	}
}

func (a *adminAPI) Provision(ctx caddy.Context) error {
	a.ctx = ctx
	a.logger = ctx.Logger(a).Sugar()

	app, err := ctx.App(moduleName)
	if err != nil {
		return err
	}

	a.app = app.(*SouinApp)

	return nil
}

Removed from Provision(): the eager InternalEndpointHandlers = api.GenerateHandlerMap(...) call. Everything else in Provision() stays the same.

Patched and running this in production (built via a local xcaddy override on top of the v0.16.0 tag) without further panics since.

Additional Context

Related to #140 — same symptom (nil pointer panic in the admin API), and the fix here is the same shape (lazy-init instead of eager-init at Provision() time). Flagging as its own issue since it's still present on v0.16.0 and #140 seems to have gone quiet — happy to close this in favor of #140 or open a PR directly if that's more useful.

主要言語
Go
スター
396
フォーク
29
平均マージ
32分
マージ済み PR(30日)
1

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

caddyserver/cache-handler のほかの issue

caddyserver/cache-handler の issue をすべて見る

似ている issue

Go の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。