From 4d13ff3622d4e3bba62df73d1fd6f2cc7dc6c4e6 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 3 Nov 2023 21:20:32 +0200 Subject: [PATCH] improvements of the new ContextWrapper --- HISTORY.md | 2 +- _examples/routing/custom-context/main.go | 78 +++++++------ context_wrapper.go | 134 +++++++++++++++-------- go.mod | 4 +- go.sum | 12 +- 5 files changed, 143 insertions(+), 87 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index ab79cfcf..766c8612 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -23,7 +23,7 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene Changes apply to `main` branch. -- A new way to customize the handler's parameter among with the `hero` and `mvc` packages. New `iris.NewContextWrapper` method and `iris.DefaultContextPool` struct were added to wrap a handler and use a custom context instead of the iris.Context directly. +- A new way to customize the handler's parameter among with the `hero` and `mvc` packages. New `iris.NewContextWrapper` and `iris.NewContextPool` methods were added to wrap a handler and use a custom context instead of the iris.Context directly. Example at: https://github.com/kataras/iris/tree/main/_examples/routing/custom-context. - The `cache` sub-package has an update, after 4 years: diff --git a/_examples/routing/custom-context/main.go b/_examples/routing/custom-context/main.go index 89b9c207..6148325d 100644 --- a/_examples/routing/custom-context/main.go +++ b/_examples/routing/custom-context/main.go @@ -1,6 +1,8 @@ package main import ( + "sync" + "github.com/kataras/iris/v12" ) @@ -8,22 +10,12 @@ func main() { // 1. Create the iris app instance. app := iris.New() - /* - w := iris.NewContextWrapper(&iris.DefaultContextPool[*myCustomContext]{ - AcquireFunc: func(ctx iris.Context) *myCustomContext { - return &myCustomContext{ - Context: ctx, - // custom fields here... - } - }, - ReleaseFunc: func(t *myCustomContext) { - // do nothing - }, - }) - OR: */ // 2. Create the Context Wrapper which will be used to wrap the handlers // that expect a *myCustomContext instead of iris.Context. w := iris.NewContextWrapper(&myCustomContextPool{}) + // OR: + // w := iris.NewContextWrapper(iris.NewContextPool[myCustomContext, *myCustomContext]()) + // The example custom context pool operates exactly the same as the result of iris.NewContextPool. // 3. Register the handler(s) which expects a *myCustomContext instead of iris.Context. // The `w.Handler` will wrap the handler and will call the `Acquire` and `Release` @@ -38,6 +30,38 @@ func index(ctx *myCustomContext) { ctx.HTML("

Hello, World!

") } +/* + Custom Context Pool +*/ +// Create the context sync pool for our custom context, +// the pool must implement Acquire() T and Release(T) methods to satisfy the iris.ContextPool interface. +type myCustomContextPool struct { + pool sync.Pool +} + +// Acquire returns a new custom context from the pool. +func (p *myCustomContextPool) Acquire(ctx iris.Context) *myCustomContext { + v := p.pool.Get() + if v == nil { + v = &myCustomContext{ + Context: ctx, + // custom fields here... + } + } + + return v.(*myCustomContext) +} + +// Release puts a custom context back to the pool. +func (p *myCustomContextPool) Release(t *myCustomContext) { + // You can take advantage of this method to clear the context + // and re-use it on the Acquire method, use the sync.Pool. + p.pool.Put(t) +} + +/* + Custom Context +*/ // Create a custom context. type myCustomContext struct { // It's just an embedded field which is set on AcquireFunc, @@ -47,28 +71,16 @@ type myCustomContext struct { iris.Context } +// SetContext sets the original iris.Context, +// should be implemented by custom context type(s) when +// the ContextWrapper uses a context Pool through the iris.NewContextPool function. +// Comment line 15, uncomment line 17 and the method below. +func (c *myCustomContext) SetContext(ctx iris.Context) { + c.Context = ctx +} + func (c *myCustomContext) HTML(format string, args ...interface{}) (int, error) { c.Application().Logger().Info("HTML was called from custom Context") return c.Context.HTML(format, args...) } - -// Create the context memory pool for your custom context, -// the pool must contain Acquire() T and Release(T) methods. -type myCustomContextPool struct{} - -// Acquire returns a new custom context from the pool. -func (p *myCustomContextPool) Acquire(ctx iris.Context) *myCustomContext { - return &myCustomContext{ - Context: ctx, - // custom fields here... - } -} - -// Release puts a custom context back to the pool. -func (p *myCustomContextPool) Release(t *myCustomContext) { - // You can take advantage of this method to clear the context - // and re-use it on the Acquire method, use the sync.Pool. - // - // We do nothing for the shake of the exampel. -} diff --git a/context_wrapper.go b/context_wrapper.go index 67b718aa..6315ab61 100644 --- a/context_wrapper.go +++ b/context_wrapper.go @@ -1,51 +1,101 @@ package iris import ( + "sync" + "github.com/kataras/iris/v12/context" ) -// ContextPool is a pool of T. +type ( + // ContextSetter is an interface which can be implemented by a struct + // to set the iris.Context to the struct. + // The receiver must be a pointer of the struct. + ContextSetter interface { + // SetContext sets the iris.Context to the struct. + SetContext(Context) + } + + // ContextSetterPtr is a pointer of T which implements the `ContextSetter` interface. + // The T must be a struct. + ContextSetterPtr[T any] interface { + *T + ContextSetter + } + + // emptyContextSetter is an empty struct which implements the `ContextSetter` interface. + emptyContextSetter struct{} +) + +// SetContext method implements `ContextSetter` interface. +func (*emptyContextSetter) SetContext(Context) {} + +// ContextPool is a pool of T. It's used to acquire and release custom context. +// Use of custom implementation or `NewContextPool`. // -// See `NewContextWrapper` and `ContextPool` for more. -type ContextPool[T any] interface { - Acquire(ctx Context) T - Release(T) -} - -// DefaultContextPool is a pool of T. -// It's used to acquire and release T. -// The T is acquired from the pool and released back to the pool after the handler's execution. -// The T is passed to the handler as an argument. -// The T is not shared between requests. -type DefaultContextPool[T any] struct { - AcquireFunc func(Context) T - ReleaseFunc func(T) -} - -// Ensure that DefaultContextPool[T] implements ContextPool[T]. -var _ ContextPool[any] = (*DefaultContextPool[any])(nil) - -// Acquire returns a new T from the pool's AcquireFunc. -func (p *DefaultContextPool[T]) Acquire(ctx Context) T { - acquire := p.AcquireFunc - if p.AcquireFunc == nil { - acquire = func(ctx Context) T { - var t T - return t - } +// See `NewContextWrapper` and `NewContextPool` for more. +type ( + ContextPool[T any] interface { + // Acquire must return a new T from a pool. + Acquire(ctx Context) T + // Release must put the T back to the pool. + Release(T) } - return acquire(ctx) + // syncContextPool is a sync pool implementation of T. + // It's used to acquire and release T. + // The contextPtr is acquired from the sync pool and released back to the sync pool after the handler's execution. + // The contextPtr is passed to the handler as an argument. + // ThecontextPtr is not shared between requests. + // The contextPtr must implement the `ContextSetter` interface. + // The T must be a struct. + // The contextPtr must be a pointer of T. + syncContextPool[T any, contextPtr ContextSetterPtr[T]] struct { + pool *sync.Pool + } +) + +// Ensure that syncContextPool implements ContextPool. +var _ ContextPool[*emptyContextSetter] = (*syncContextPool[emptyContextSetter, *emptyContextSetter])(nil) + +// NewContextPool returns a new ContextPool default implementation which +// uses sync.Pool to implement its Acquire and Release methods. +// The contextPtr is acquired from the sync pool and released back to the sync pool after the handler's execution. +// The contextPtr is passed to the handler as an argument. +// ThecontextPtr is not shared between requests. +// The contextPtr must implement the `ContextSetter` interface. +// The T must be a struct. +// The contextPtr must be a pointer of T. +// +// Example: +// w := iris.NewContextWrapper(iris.NewContextPool[myCustomContext, *myCustomContext]()) +func NewContextPool[T any, contextPtr ContextSetterPtr[T]]() ContextPool[contextPtr] { + return &syncContextPool[T, contextPtr]{ + pool: &sync.Pool{ + New: func() interface{} { + var t contextPtr = new(T) + return t + }, + }, + } } -// Release does nothing if the pool's ReleaseFunc is nil. -func (p *DefaultContextPool[T]) Release(t T) { - release := p.ReleaseFunc - if p.ReleaseFunc == nil { - release = func(t T) {} - } +// Acquire returns a new T from the sync pool. +func (p *syncContextPool[T, contextPtr]) Acquire(ctx Context) contextPtr { + // var t contextPtr + // if v := p.pool.Get(); v == nil { + // t = new(T) + // } else { + // t = v.(contextPtr) + // } - release(t) + t := p.pool.Get().(contextPtr) + t.SetContext(ctx) + return t +} + +// Release puts the T back to the sync pool. +func (p *syncContextPool[T, contextPtr]) Release(t contextPtr) { + p.pool.Put(t) } // ContextWrapper is a wrapper for handlers which expect a T instead of iris.Context. @@ -60,19 +110,13 @@ type ContextWrapper[T any] struct { // The default pool's AcquireFunc returns a zero value of T. // The default pool's ReleaseFunc does nothing. // The default pool is used when the pool is nil. -// Use the `&iris.DefaultContextPool{...}` to pass a simple context pool. +// Use the `iris.NewContextPool[T, *T]()` to pass a simple context pool. +// Then, use the `Handler` method to wrap custom handlers to iris ones. // -// See the `Handler` method for more. // Example: https://github.com/kataras/iris/tree/main/_examples/routing/custom-context func NewContextWrapper[T any](pool ContextPool[T]) *ContextWrapper[T] { if pool == nil { - pool = &DefaultContextPool[T]{ - AcquireFunc: func(ctx Context) T { - var t T - return t - }, - ReleaseFunc: func(t T) {}, - } + panic("pool cannot be nil") } return &ContextWrapper[T]{ diff --git a/go.mod b/go.mod index dbff4767..7089e814 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/redis/go-redis/v9 v9.3.0 github.com/schollz/closestmatch v2.1.0+incompatible github.com/shirou/gopsutil/v3 v3.23.10 - github.com/tdewolff/minify/v2 v2.20.5 + github.com/tdewolff/minify/v2 v2.20.6 github.com/vmihailenco/msgpack/v5 v5.4.1 github.com/yosssi/ace v0.0.5 go.etcd.io/bbolt v1.3.8 @@ -95,7 +95,7 @@ require ( github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/stretchr/testify v1.8.4 // indirect - github.com/tdewolff/parse/v2 v2.7.3-0.20231031132452-e7c20a5d77ab // indirect + github.com/tdewolff/parse/v2 v2.7.4 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/go.sum b/go.sum index 80ff493e..862bd486 100644 --- a/go.sum +++ b/go.sum @@ -224,12 +224,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= -github.com/tdewolff/minify/v2 v2.20.5 h1:IbJpmpAFESnuJPdsvFBJWsDcXE5qHsmaVQrRqhOI9sI= -github.com/tdewolff/minify/v2 v2.20.5/go.mod h1:N78HtaitkDYAWXFbqhWX/LzgwylwudK0JvybGDVQ+Mw= -github.com/tdewolff/parse/v2 v2.7.3-0.20231031132452-e7c20a5d77ab h1:4zj+h84OrVW4pljmp+LABknN7VS1IMAbeHj+eckO6Ao= -github.com/tdewolff/parse/v2 v2.7.3-0.20231031132452-e7c20a5d77ab/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9awHSm84h8= -github.com/tdewolff/test v1.0.10 h1:uWiheaLgLcNFqHcdWveum7PQfMnIUTf9Kl3bFxrIoew= -github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tdewolff/minify/v2 v2.20.6 h1:R4+Iw1ZqJxrqH52WWHtCpukMuhmO/EasY8YlDiSxphw= +github.com/tdewolff/minify/v2 v2.20.6/go.mod h1:9t0EY9xySGt1vrP8iscmJfywQwDCQyQBYN6ge+9GwP0= +github.com/tdewolff/parse/v2 v2.7.4 h1:zrUn2CFg9+5llbUZcsycctFlNRyV1D5gFBZRxuGzdzk= +github.com/tdewolff/parse/v2 v2.7.4/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= +github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA= +github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=