commit 1e3c1c1446cbce1da5750960baa03fa30e86aa42 Author: crusader Date: Tue Sep 5 14:42:54 2017 +0900 Project has been created diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3733e36 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# Created by .ignore support plugin (hsz.mobi) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Go template +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ +.idea/ +*.iml + +vendor/ +glide.lock +.DS_Store +dist/ +debug diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2ca2b1d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${workspaceRoot}/main.go", + "env": {}, + "args": [], + "showLog": true + }, + { + "name": "File Debug", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${fileDirname}", + "env": {}, + "args": [], + "showLog": true + } + + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..46c249c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +// Place your settings in this file to overwrite default and user settings. +{ + // Specifies Lint tool name. + "go.lintTool": "gometalinter", + + // Flags to pass to Lint tool (e.g. ["-min_confidence=.8"]) + "go.lintFlags": [ + "--config=${workspaceRoot}/golint.json" + ] + +} \ No newline at end of file diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..a1eb558 --- /dev/null +++ b/glide.yaml @@ -0,0 +1,3 @@ +package: git.loafle.net/commons_go/grpc_pool +import: +- package: google.golang.org/grpc diff --git a/pool.go b/pool.go new file mode 100644 index 0000000..392408c --- /dev/null +++ b/pool.go @@ -0,0 +1,171 @@ +package overflow_grpc_pool + +import ( + "context" + "errors" + "time" + + "google.golang.org/grpc" +) + +var ( + // ErrFullPool is the error when the pool is already full + ErrPoolFull = errors.New("grpc pool: Pool is reached maximum capacity.") +) + +type pooledInstance struct { + clientConn *grpc.ClientConn + idleTime time.Time + instance interface{} + inUse bool +} + +type Pool interface { + Get() (interface{}, error) + Put(i interface{}) + Capacity() int + Available() int + Destroy() +} + +type pool struct { + ctx context.Context + handler PoolHandler + instances map[interface{}]*pooledInstance + instancesCh chan *pooledInstance +} + +func New(ctx context.Context, handler PoolHandler) (Pool, error) { + var err error + p := &pool{ + ctx: ctx, + handler: handler, + instances: make(map[interface{}]*pooledInstance, handler.GetMaxCapacity()), + instancesCh: make(chan *pooledInstance, handler.GetMaxCapacity()), + } + + if 0 < handler.GetMaxIdle() { + for i := 0; i < handler.GetMaxIdle(); i++ { + err = p.createInstance() + if nil != err { + return nil, err + } + } + } + + return p, nil +} + +func (p *pool) createInstance() error { + maxCapacity := p.handler.GetMaxCapacity() + currentInstanceCount := len(p.instances) + if currentInstanceCount >= maxCapacity { + return ErrPoolFull + } + + conn, i, err := p.handler.OnCreate() + if err != nil { + return err + } + pi := &pooledInstance{ + clientConn: conn, + idleTime: time.Now(), + instance: i, + inUse: false, + } + p.instances[i] = pi + p.instancesCh <- pi + + return nil +} + +func (p *pool) destroyInstance(i interface{}) error { + var err error + pi, ok := p.instances[i] + if !ok { + return nil + } + err = pi.clientConn.Close() + if nil != err { + return err + } + delete(p.instances, i) + + return nil +} + +func (p *pool) get() (*pooledInstance, error) { + var pi *pooledInstance + var err error + + avail := len(p.instancesCh) + if 0 == avail { + if err = p.createInstance(); nil != err { + return nil, err + } + } + + for { + select { + case pi = <-p.instancesCh: + // All good + default: + } + idleTimeout := p.handler.GetIdleTimeout() + if 1 < len(p.instancesCh) && 0 < idleTimeout && pi.idleTime.Add(idleTimeout).Before(time.Now()) { + go p.destroyInstance(pi.instance) + continue + } + break + } + + return pi, nil +} + +func (p *pool) Get() (interface{}, error) { + var err error + var pi *pooledInstance + if pi, err = p.get(); nil != err { + return nil, err + } + + pi.inUse = true + + return pi.instance, nil +} + +func (p *pool) Put(i interface{}) { + pi, ok := p.instances[i] + if !ok { + return + } + + pi.idleTime = time.Now() + pi.inUse = false + + select { + case p.instancesCh <- pi: + // All good + default: + // channel is full + return + } + +} + +func (p *pool) Capacity() int { + return cap(p.instancesCh) +} + +func (p *pool) Available() int { + return len(p.instancesCh) +} + +func (p *pool) Destroy() { + close(p.instancesCh) + + for k, _ := range p.instances { + p.destroyInstance(k) + } + +} diff --git a/pool_handler.go b/pool_handler.go new file mode 100644 index 0000000..72986c8 --- /dev/null +++ b/pool_handler.go @@ -0,0 +1,23 @@ +package overflow_grpc_pool + +import ( + "time" + + "google.golang.org/grpc" +) + +const ( + // DefaultWriteTimeout is default value of Write Timeout + DefaultIdleTimeout = 0 + DefaultMaxIdle = 0 + DefaultMaxCapacity = 1 +) + +type PoolHandler interface { + GetIdleTimeout() time.Duration + GetMaxIdle() int + GetMaxCapacity() int + + OnCreate() (*grpc.ClientConn, interface{}, error) + Validate() +} diff --git a/pool_handlers.go b/pool_handlers.go new file mode 100644 index 0000000..79116e9 --- /dev/null +++ b/pool_handlers.go @@ -0,0 +1,41 @@ +package overflow_grpc_pool + +import ( + "time" + + "google.golang.org/grpc" +) + +// Options is configuration of the Pool of GRpc client +type PoolHandlers struct { + IdleTimeout time.Duration + MaxIdle int + MaxCapacity int +} + +func (h *PoolHandlers) GetIdleTimeout() time.Duration { + return h.IdleTimeout +} +func (h *PoolHandlers) GetMaxIdle() int { + return h.MaxIdle +} +func (h *PoolHandlers) GetMaxCapacity() int { + return h.MaxCapacity +} +func (h *PoolHandlers) OnCreate() (*grpc.ClientConn, interface{}, error) { + return nil, nil, nil +} + +// Validate validates the configuration +func (o *PoolHandlers) Validate() { + if o.IdleTimeout < 0 { + o.IdleTimeout = DefaultIdleTimeout + } + if o.MaxIdle < 0 { + o.MaxIdle = DefaultMaxIdle + } + if o.MaxCapacity <= 0 { + o.MaxCapacity = DefaultMaxCapacity + } + +}