├── driver └── driver.go ├── .travis.yml ├── .gitignore ├── LICENSE ├── godis └── driver.go ├── go-redis └── driver.go ├── redisv2 └── driver.go ├── README.md ├── redigo └── driver.go ├── resque.go └── hoisie └── driver.go /driver/driver.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import "time" 4 | 5 | type Enqueuer interface { 6 | SetClient(name string, client interface{}) 7 | ListPush(queue string, jobJSON string) (listLength int64, err error) 8 | ListPushDelay(t time.Time, queue string, jobJSON string) (bool, error) 9 | Poll() 10 | } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: go 4 | 5 | go: 6 | - 1.4 7 | - 1.5 8 | - 1.6 9 | - 1.7 10 | - 1.8 11 | - tip 12 | 13 | os: 14 | - linux 15 | 16 | env: 17 | matrix: 18 | - CPU=8 19 | 20 | script: 21 | - go test -v -cpu=$CPU -benchmem -bench=. 22 | 23 | matrix: 24 | allow_failures: 25 | - go: tip 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | .DS_Store 25 | pkg/ 26 | src/ 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Max Riveiro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /godis/driver.go: -------------------------------------------------------------------------------- 1 | package resque 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "time" 7 | 8 | "github.com/kavu/go-resque" 9 | "github.com/kavu/go-resque/driver" 10 | "github.com/simonz05/godis/redis" 11 | ) 12 | 13 | func init() { 14 | resque.Register("godis", &drv{}) 15 | } 16 | 17 | type drv struct { 18 | client *redis.Client 19 | driver.Enqueuer 20 | schedule map[string]struct{} 21 | nameSpace string 22 | } 23 | 24 | func (d *drv) SetClient(name string, client interface{}) { 25 | d.client = client.(*redis.Client) 26 | d.schedule = make(map[string]struct{}) 27 | d.nameSpace = name 28 | } 29 | 30 | func (d *drv) ListPush(queue string, jobJSON string) (int64, error) { 31 | return d.client.Rpush(d.nameSpace+"queue:"+queue, jobJSON) 32 | } 33 | 34 | func (d *drv) ListPushDelay(t time.Time, queue string, jobJSON string) (bool, error) { 35 | _, err := d.client.Zadd(queue, timeToSecondsWithNanoPrecision(t), jobJSON) 36 | if err != nil { 37 | return false, err 38 | } 39 | if _, ok := d.schedule[queue]; !ok { 40 | d.schedule[queue] = struct{}{} 41 | } 42 | return true, nil 43 | } 44 | func timeToSecondsWithNanoPrecision(t time.Time) float64 { 45 | return float64(t.UnixNano()) / 1000000000.0 // nanoSecondPrecision 46 | } 47 | 48 | func (d *drv) Poll() { 49 | go func(d *drv) { 50 | for { 51 | for key := range d.schedule { 52 | now := timeToSecondsWithNanoPrecision(time.Now()) 53 | r, _ := d.client.Zrangebyscore(key, "-inf", 54 | strconv.FormatFloat(now, 'E', -1, 64)) 55 | jobs := r.StringArray() 56 | if len(jobs) == 0 { 57 | continue 58 | } 59 | if removed, _ := d.client.Zrem(key, jobs[0]); removed { 60 | queue := strings.TrimPrefix(key, d.nameSpace) 61 | d.client.Lpush(d.nameSpace+"queue:"+queue, jobs[0]) 62 | } 63 | } 64 | time.Sleep(100 * time.Millisecond) 65 | } 66 | }(d) 67 | } 68 | -------------------------------------------------------------------------------- /go-redis/driver.go: -------------------------------------------------------------------------------- 1 | package resque 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/fiorix/go-redis/redis" 9 | "github.com/kavu/go-resque" 10 | "github.com/kavu/go-resque/driver" 11 | ) 12 | 13 | func init() { 14 | resque.Register("redis-go", &drv{}) 15 | } 16 | 17 | type drv struct { 18 | client *redis.Client 19 | driver.Enqueuer 20 | schedule map[string]struct{} 21 | nameSpace string 22 | } 23 | 24 | func (d *drv) SetClient(name string, client interface{}) { 25 | d.client = client.(*redis.Client) 26 | d.schedule = make(map[string]struct{}) 27 | d.nameSpace = name 28 | } 29 | 30 | func (d *drv) ListPush(queue string, jobJSON string) (int64, error) { 31 | // Ensure the queue exists 32 | _, err := d.client.SAdd(d.nameSpace+"queues", queue) 33 | 34 | listLength, err := d.client.RPush(d.nameSpace+"queue:"+queue, jobJSON) 35 | if err != nil { 36 | return -1, err 37 | } 38 | 39 | return int64(listLength), err 40 | } 41 | func (d *drv) ListPushDelay(t time.Time, queue string, jobJSON string) (bool, error) { 42 | _, err := d.client.ZAdd(queue, t.UnixNano(), jobJSON) 43 | if err != nil { 44 | return false, err 45 | } 46 | if _, ok := d.schedule[queue]; !ok { 47 | d.schedule[queue] = struct{}{} 48 | } 49 | return true, nil 50 | } 51 | 52 | func (d *drv) Poll() { 53 | go func(d *drv) { 54 | for { 55 | for key := range d.schedule { 56 | now := time.Now() 57 | k := fmt.Sprintf("%s -inf %d", key, now.UnixNano()) 58 | jobs, _ := d.client.ZRangeByScore(k, 0, 1, true, true, 0, 1) 59 | if len(jobs) == 0 { 60 | continue 61 | } 62 | removed, _ := d.client.ZRem(key, jobs[0]) 63 | if removed == 0 { 64 | queue := strings.TrimPrefix(key, d.nameSpace) 65 | d.client.LPush(d.nameSpace+"queue:"+queue, jobs[0]) 66 | } 67 | } 68 | time.Sleep(100 * time.Millisecond) 69 | } 70 | }(d) 71 | } 72 | -------------------------------------------------------------------------------- /redisv2/driver.go: -------------------------------------------------------------------------------- 1 | package resque 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "github.com/hoisie/redis" 8 | "github.com/kavu/go-resque" 9 | "github.com/kavu/go-resque/driver" 10 | ) 11 | 12 | func init() { 13 | resque.Register("redisv2", &drv{}) 14 | } 15 | 16 | type drv struct { 17 | client *redis.Client 18 | driver.Enqueuer 19 | schedule map[string]struct{} 20 | nameSpace string 21 | } 22 | 23 | func (d *drv) SetClient(name string, client interface{}) { 24 | d.client = client.(*redis.Client) 25 | d.schedule = make(map[string]struct{}) 26 | d.nameSpace = name 27 | } 28 | 29 | func (d *drv) ListPush(queue string, jobJSON string) (int64, error) { 30 | return 1, d.client.Rpush(d.nameSpace+"queue:"+queue, []byte(jobJSON)) 31 | } 32 | 33 | func (d *drv) ListPushDelay(t time.Time, queue string, jobJSON string) (bool, error) { 34 | _, err := d.client.Zadd(queue, []byte(jobJSON), timeToSecondsWithNanoPrecision(t)) 35 | if err != nil { 36 | return false, err 37 | } 38 | if _, ok := d.schedule[queue]; !ok { 39 | d.schedule[queue] = struct{}{} 40 | } 41 | return true, nil 42 | } 43 | func timeToSecondsWithNanoPrecision(t time.Time) float64 { 44 | return float64(t.UnixNano()) / 1000000000.0 // nanoSecondPrecision 45 | } 46 | 47 | func (d *drv) Poll() { 48 | go func(d *drv) { 49 | for { 50 | for key := range d.schedule { 51 | now := timeToSecondsWithNanoPrecision(time.Now()) 52 | r, _ := d.client.Zrangebyscore(key+"-inf", now, 1) 53 | var jobs []string 54 | for _, job := range r { 55 | jobs = append(jobs, string(job)) 56 | } 57 | if len(jobs) == 0 { 58 | continue 59 | } 60 | if removed, _ := d.client.Zrem(key, []byte(jobs[0])); removed { 61 | queue := strings.TrimPrefix(key, d.nameSpace) 62 | d.client.Lpush(d.nameSpace+"queue:"+queue, []byte(jobs[0])) 63 | } 64 | } 65 | time.Sleep(100 * time.Millisecond) 66 | } 67 | }(d) 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-resque 2 | 3 | Simple [Resque](https://github.com/defunkt/resque) queue client for [Go](http://golang.org). 4 | 5 | ## Installation 6 | 7 | Installation is simple and familiar for Go programmers: 8 | 9 | ``` 10 | go get github.com/kavu/go-resque 11 | ``` 12 | 13 | ## Usage 14 | 15 | Let's assume that you have such Resque Job (taken from Resque examples): 16 | 17 | ```ruby 18 | module Demo 19 | class Job 20 | def self.perform(params) 21 | puts "Processed a job!" 22 | end 23 | end 24 | end 25 | ``` 26 | 27 | So, we can enqueue this job from Go. 28 | 29 | ```go 30 | package main 31 | 32 | import ( 33 | "github.com/kavu/go-resque" // Import this package 34 | _ "github.com/kavu/go-resque/godis" // Use godis driver 35 | "github.com/simonz05/godis/redis" // Redis client from godis package 36 | ) 37 | 38 | func main() { 39 | var err error 40 | 41 | client := redis.New("tcp:127.0.0.1:6379", 0, "") // Create new Redis client to use for enqueuing 42 | enqueuer := resque.NewRedisEnqueuer("godis", client) // Create enqueuer instance 43 | 44 | // Enqueue the job into the "go" queue with appropriate client 45 | _, err = enqueuer.Enqueue("resque:queue:go", "Demo::Job") 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | // Enqueue into the "default" queue with passing one parameter to the Demo::Job.perform 51 | _, err = enqueuer.Enqueue("resque:queue:default", "Demo::Job", 1) 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | // Enqueue into the "extra" queue with passing multiple 57 | // parameters to the Demo::Job.perform so it will fail 58 | _, err = enqueuer.Enqueue("resque:queue:extra", "Demo::Job", 1, 2, "woot") 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | } 64 | ``` 65 | 66 | Simple enough? I hope so. 67 | 68 | ## Contributing 69 | 70 | Just open pull request or ping me directly on e-mail, if you want to discuss some ideas. 71 | -------------------------------------------------------------------------------- /redigo/driver.go: -------------------------------------------------------------------------------- 1 | package resque 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "time" 7 | 8 | "github.com/garyburd/redigo/redis" 9 | "github.com/kavu/go-resque" 10 | "github.com/kavu/go-resque/driver" 11 | ) 12 | 13 | func init() { 14 | resque.Register("redigo", &drv{}) 15 | } 16 | 17 | type drv struct { 18 | client *redis.Conn 19 | driver.Enqueuer 20 | schedule map[string]struct{} 21 | nameSpace string 22 | } 23 | 24 | func (d *drv) SetClient(name string, client interface{}) { 25 | d.client = client.(*redis.Conn) 26 | d.schedule = make(map[string]struct{}) 27 | d.nameSpace = name 28 | } 29 | 30 | func (d *drv) ListPush(queue string, jobJSON string) (int64, error) { 31 | resp, err := (*d.client).Do("RPUSH", d.nameSpace+"queue:"+queue, jobJSON) 32 | if err != nil { 33 | return -1, err 34 | } 35 | 36 | return redis.Int64(resp, err) 37 | } 38 | func (d *drv) ListPushDelay(t time.Time, queue string, jobJSON string) (bool, error) { 39 | _, err := (*d.client).Do("ZADD", queue, timeToSecondsWithNanoPrecision(t), jobJSON) 40 | if err != nil { 41 | return false, err 42 | } 43 | if _, ok := d.schedule[queue]; !ok { 44 | d.schedule[queue] = struct{}{} 45 | } 46 | return true, nil 47 | } 48 | func timeToSecondsWithNanoPrecision(t time.Time) float64 { 49 | return float64(t.UnixNano()) / 1000000000.0 //nanoSecondPrecision 50 | } 51 | 52 | func (d *drv) Poll() { 53 | go func(d *drv) { 54 | for { 55 | for key := range d.schedule { 56 | now := timeToSecondsWithNanoPrecision(time.Now()) 57 | jobs, _ := redis.Strings((*d.client).Do("ZRANGEBYSCORE", key, "-inf", 58 | strconv.FormatFloat(now, 'E', -1, 64))) 59 | if len(jobs) == 0 { 60 | continue 61 | } 62 | if _, err := (*d.client).Do("ZREM", key, jobs[0]); err != nil { 63 | queue := strings.TrimPrefix(key, d.nameSpace) 64 | (*d.client).Do("LPUSH", d.nameSpace+"queue:"+queue, jobs[0]) 65 | } 66 | } 67 | time.Sleep(100 * time.Millisecond) 68 | } 69 | }(d) 70 | } 71 | -------------------------------------------------------------------------------- /resque.go: -------------------------------------------------------------------------------- 1 | package resque 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | "github.com/kavu/go-resque/driver" 8 | ) 9 | 10 | var drivers = make(map[string]driver.Enqueuer) 11 | 12 | type jobArg interface{} 13 | 14 | type MockRedisDriver struct { 15 | driver.Enqueuer 16 | } 17 | 18 | type job struct { 19 | Queue string `json:"queue,omitempty"` 20 | Class string `json:"class"` 21 | Args []jobArg `json:"args"` 22 | } 23 | 24 | func Register(name string, driver driver.Enqueuer) { 25 | if _, d := drivers[name]; d { 26 | panic("Register called twice for driver " + name) 27 | } 28 | drivers[name] = driver 29 | } 30 | 31 | func NewRedisEnqueuer(drvName string, client interface{}, nameSpace string) *RedisEnqueuer { 32 | drv, ok := drivers[drvName] 33 | if !ok { 34 | panic("No such driver: " + drvName) 35 | } 36 | 37 | drv.SetClient(nameSpace, client) 38 | drv.Poll() 39 | return &RedisEnqueuer{drv: drv} 40 | } 41 | 42 | type RedisEnqueuer struct { 43 | drv driver.Enqueuer 44 | } 45 | 46 | func (enqueuer *RedisEnqueuer) Enqueue(queue, jobClass string, args ...jobArg) (int64, error) { 47 | // NOTE: Dirty hack to make a [{}] JSON struct 48 | if len(args) == 0 { 49 | args = append(make([]jobArg, 0), make(map[string]jobArg, 0)) 50 | } 51 | 52 | jobJSON, err := json.Marshal(&job{Class: jobClass, Args: args}) 53 | if err != nil { 54 | return -1, err 55 | } 56 | 57 | return enqueuer.drv.ListPush(queue, string(jobJSON)) 58 | } 59 | 60 | // EnqueueIn enque a job at a duration 61 | func (enqueuer *RedisEnqueuer) EnqueueIn(delay time.Duration, queue, jobClass string, args ...jobArg) (bool, error) { 62 | enqueueTime := time.Now().Add(delay) 63 | 64 | if len(args) == 0 { 65 | args = append(make([]jobArg, 0), make(map[string]jobArg, 0)) 66 | } 67 | 68 | jobJSON, err := json.Marshal(&job{Class: jobClass, Args: args, Queue: queue}) 69 | if err != nil { 70 | return false, err 71 | } 72 | return enqueuer.drv.ListPushDelay(enqueueTime, queue, string(jobJSON)) 73 | } 74 | -------------------------------------------------------------------------------- /hoisie/driver.go: -------------------------------------------------------------------------------- 1 | package resque 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "github.com/hoisie/redis" 8 | "github.com/kavu/go-resque" 9 | "github.com/kavu/go-resque/driver" 10 | ) 11 | 12 | func init() { 13 | resque.Register("hoisie", &drv{}) 14 | } 15 | 16 | type drv struct { 17 | client *redis.Client 18 | driver.Enqueuer 19 | schedule map[string]struct{} 20 | nameSpace string 21 | } 22 | 23 | func (d *drv) SetClient(name string, client interface{}) { 24 | d.client = client.(*redis.Client) 25 | d.schedule = make(map[string]struct{}) 26 | d.nameSpace = name 27 | } 28 | 29 | func (d *drv) ListPush(queue string, jobJSON string) (int64, error) { 30 | err := d.client.Rpush(d.nameSpace+"queue:"+queue, []byte(jobJSON)) 31 | if err != nil { 32 | return -1, err 33 | } 34 | 35 | listLength, err := d.client.Llen(queue) 36 | 37 | return int64(listLength), err 38 | } 39 | 40 | func (d *drv) ListPushDelay(t time.Time, queue string, jobJSON string) (bool, error) { 41 | _, err := d.client.Zadd(queue, []byte(jobJSON), timeToSecondsWithNanoPrecision(t)) 42 | if err != nil { 43 | return false, err 44 | } 45 | if _, ok := d.schedule[queue]; !ok { 46 | d.schedule[queue] = struct{}{} 47 | } 48 | return true, nil 49 | } 50 | func timeToSecondsWithNanoPrecision(t time.Time) float64 { 51 | return float64(t.UnixNano()) / 1000000000.0 //nanoSecondPrecision 52 | } 53 | 54 | func (d *drv) Poll() { 55 | go func(d *drv) { 56 | for { 57 | for key := range d.schedule { 58 | now := timeToSecondsWithNanoPrecision(time.Now()) 59 | r, _ := d.client.Zrangebyscore(key+"-inf", 60 | now, 1) 61 | var jobs []string 62 | for _, job := range r { 63 | jobs = append(jobs, string(job)) 64 | } 65 | if len(jobs) == 0 { 66 | continue 67 | } 68 | if removed, _ := d.client.Zrem(key, []byte(jobs[0])); removed { 69 | queue := strings.TrimPrefix(key, d.nameSpace) 70 | d.client.Lpush(d.nameSpace+"queue:"+queue, []byte(jobs[0])) 71 | } 72 | } 73 | time.Sleep(100 * time.Millisecond) 74 | } 75 | }(d) 76 | } 77 | --------------------------------------------------------------------------------