Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Lint
name: CI

on:
push:
Expand All @@ -7,6 +7,18 @@ on:
branches: [ master, v2 ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'

- name: Run tests
run: go test ./... -count=1 -race

lint:
runs-on: ubuntu-latest

Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ err := dix.TryInject(di, func(svc *Service) {
})
```

### Thread Safety

`Dix` containers are **not thread-safe**. Do not call `Provide` / `Inject` (or their `Try*` variants) concurrently on the same container instance.

Recommended usage:

- Register all providers during application startup (single goroutine).
- After startup, only read resolved dependencies, or continue injection from a single goroutine.
- Use separate `Dix` instances per goroutine if you need isolated containers.
- For a process-wide singleton, prefer `dixglobal` only when startup is single-threaded.

### Startup Timeout / Slow Provider Warning

Control long-running providers during startup:
Expand Down
11 changes: 11 additions & 0 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ err := dix.TryInject(di, func(svc *Service) {
})
```

### 线程安全

`Dix` 容器**不是线程安全的**。请勿在同一容器实例上并发调用 `Provide` / `Inject`(及其 `Try*` 变体)。

推荐用法:

- 在应用启动阶段(单 goroutine)完成全部 provider 注册。
- 启动完成后仅读取已解析的依赖,或继续在单 goroutine 中注入。
- 若需要隔离容器,请为每个 goroutine 使用独立的 `Dix` 实例。
- 进程级单例请使用 `dixglobal`,且仅在启动阶段单线程注册。

### 启动超时 / 慢 Provider 告警

可在启动阶段限制 provider 执行时间,并对慢调用输出告警:
Expand Down
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ tasks:
test:
desc: Run tests with coverage
cmds:
- go test -v ./dixinternal/... -covermode=count -coverprofile=coverage.out
- go test -v ./... -covermode=count -coverprofile=coverage.out
- go tool cover -html=coverage.out -o coverage.html

lint:
Expand Down
10 changes: 10 additions & 0 deletions dix.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,13 @@ func InjectTContext[T any](ctx context.Context, di *Dix, opts ...Option) T {
}

func Provide(di *Dix, data any) { di.Provide(data) }

func TryProvide(di *Dix, data any) error { return di.TryProvide(data) }

func TryInject(di *Dix, data any, opts ...Option) error {
return di.TryInject(data, opts...)
}

func TryInjectContext(ctx context.Context, di *Dix, data any, opts ...Option) error {
return di.TryInjectContext(ctx, data, opts...)
}
Comment on lines +84 to +90

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To complete the newly exposed Try* API suite and match the existing InjectT and InjectTContext helpers, consider adding TryInjectT and TryInjectTContext functions.

Currently, there is no safe way to inject into a struct of type T and return it along with an error without writing verbose boilerplate. Adding these helpers provides a clean, safe, and consistent way to perform struct-targeted dependency injection.

func TryInject(di *Dix, data any, opts ...Option) error {
	return di.TryInject(data, opts...)
}

func TryInjectContext(ctx context.Context, di *Dix, data any, opts ...Option) error {
	return di.TryInjectContext(ctx, data, opts...)
}

func TryInjectT[T any](di *Dix, opts ...Option) (T, error) {
	var data T
	typ := reflect.TypeOf(&data).Elem()
	if typ.Kind() != reflect.Struct {
		return data, fmt.Errorf("<T> type kind is not struct")
	}

	err := di.TryInject(&data, opts...)
	return data, err
}

func TryInjectTContext[T any](ctx context.Context, di *Dix, opts ...Option) (T, error) {
	var data T
	typ := reflect.TypeOf(&data).Elem()
	if typ.Kind() != reflect.Struct {
		return data, fmt.Errorf("<T> type kind is not struct")
	}

	err := di.TryInjectContext(ctx, &data, opts...)
	return data, err
}

49 changes: 49 additions & 0 deletions dix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package dix

import (
"testing"
)

func TestVersion(t *testing.T) {
if Version() == "" {
t.Fatal("Version() returned empty string")
}
}

func TestTryProvide(t *testing.T) {
di := New()

type svc struct{ Name string }

if err := TryProvide(di, func() *svc { return &svc{Name: "ok"} }); err != nil {
t.Fatalf("TryProvide: %v", err)
}

if err := TryProvide(di, nil); err == nil {
t.Fatal("TryProvide(nil) should return error")
}
}

func TestTryInject(t *testing.T) {
di := New()

type dep struct{ V int }

if err := TryProvide(di, func() *dep { return &dep{V: 1} }); err != nil {
t.Fatalf("TryProvide: %v", err)
}

called := false
if err := TryInject(di, func(d *dep) { called = d.V == 1 }); err != nil {
t.Fatalf("TryInject: %v", err)
}
if !called {
t.Fatal("TryInject callback was not invoked correctly")
}

if err := TryInject(di, func(*missingType) {}); err == nil {
t.Fatal("TryInject with missing dependency should return error")
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

type missingType struct{}
8 changes: 4 additions & 4 deletions dixinternal/dix.go
Original file line number Diff line number Diff line change
Expand Up @@ -1026,8 +1026,8 @@ func (dix *Dix) injectStruct(ctx context.Context, structVal reflect.Value, opt O
return nil
}

// inject is the entry point for dependency injection
// NOTE: This method is NOT thread-safe by itself. Use Inject() or TryInject() which handle locking.
// inject is the internal entry point for dependency injection.
// NOTE: The Dix container is not thread-safe; do not call Provide/Inject concurrently on the same container.
func (dix *Dix) inject(ctx context.Context, param any, opts ...Option) (err error) {
paramType := "<nil>"
component := describeComponent(param)
Expand Down Expand Up @@ -1239,8 +1239,8 @@ func parseInputType(typ reflect.Type) []*providerInputType {
return input
}

// provide registers a constructor function
// NOTE: This method is NOT thread-safe by itself. Use Provide() or TryProvide() which handle locking.
// provide registers a constructor function.
// NOTE: The Dix container is not thread-safe; do not call Provide/Inject concurrently on the same container.
func (dix *Dix) provide(param any) {
component := describeComponent(param)
logDITrace("provide.start", "component", component)
Expand Down
Loading