예제 #1
0
func ScheduledSpec(c gospec.Context) {
	scheduled := newScheduled(RETRY_KEY)

	was := Config.namespace
	Config.namespace = "prod:"

	c.Specify("empties retry queues up to the current time", func() {
		conn := Config.Pool.Get()
		defer conn.Close()

		now := time.Now().Unix()

		message1, _ := NewMsg("{\"queue\":\"default\",\"foo\":\"bar1\"}")
		message2, _ := NewMsg("{\"queue\":\"myqueue\",\"foo\":\"bar2\"}")
		message3, _ := NewMsg("{\"queue\":\"default\",\"foo\":\"bar3\"}")

		conn.Do("zadd", "prod:"+RETRY_KEY, now-60, message1.ToJson())
		conn.Do("zadd", "prod:"+RETRY_KEY, now-10, message2.ToJson())
		conn.Do("zadd", "prod:"+RETRY_KEY, now+60, message3.ToJson())

		scheduled.poll(false)

		defaultCount, _ := redis.Int(conn.Do("llen", "prod:queue:default"))
		myqueueCount, _ := redis.Int(conn.Do("llen", "prod:queue:myqueue"))
		pending, _ := redis.Int(conn.Do("zcard", "prod:"+RETRY_KEY))

		c.Expect(defaultCount, Equals, 1)
		c.Expect(myqueueCount, Equals, 1)
		c.Expect(pending, Equals, 1)
	})

	Config.namespace = was
}
예제 #2
0
func ConfigSpec(c gospec.Context) {
	var recoverOnPanic = func(f func()) (err interface{}) {
		defer func() {
			if cause := recover(); cause != nil {
				err = cause
			}
		}()

		f()

		return
	}

	c.Specify("sets redis pool size which defaults to 1", func() {
		c.Expect(Config.Pool.MaxIdle, Equals, 1)

		Configure(map[string]string{
			"server":  "localhost:6379",
			"process": "1",
			"pool":    "20",
		})

		c.Expect(Config.Pool.MaxIdle, Equals, 20)
	})

	c.Specify("can specify custom process", func() {
		c.Expect(Config.processId, Equals, "1")

		Configure(map[string]string{
			"server":  "localhost:6379",
			"process": "2",
		})

		c.Expect(Config.processId, Equals, "2")
	})

	c.Specify("requires a server parameter", func() {
		err := recoverOnPanic(func() {
			Configure(map[string]string{"process": "2"})
		})

		c.Expect(err, Equals, "Configure requires a 'server' option, which identifies a Redis instance")
	})

	c.Specify("requires a process parameter", func() {
		err := recoverOnPanic(func() {
			Configure(map[string]string{"server": "localhost:6379"})
		})

		c.Expect(err, Equals, "Configure requires a 'process' option, which uniquely identifies this instance")
	})

	c.Specify("adds ':' to the end of the namespace", func() {
		c.Expect(Config.namespace, Equals, "")

		Configure(map[string]string{
			"server":    "localhost:6379",
			"process":   "1",
			"namespace": "prod",
		})

		c.Expect(Config.namespace, Equals, "prod:")
	})
}
예제 #3
0
func MiddlewareStatsSpec(c gospec.Context) {
	var job = (func(args *Args) {
		// noop
	})

	layout := "2006-01-02"
	manager := newManager("myqueue", job, 1)
	worker := newWorker(manager)
	message, _ := NewMsg("{\"jid\":\"2\",\"retry\":true}")

	c.Specify("increments processed stats", func() {
		conn := Config.Pool.Get()
		defer conn.Close()

		count, _ := redis.Int(conn.Do("get", "stat:processed"))
		dayCount, _ := redis.Int(conn.Do("get", "stat:processed:"+time.Now().UTC().Format(layout)))

		c.Expect(count, Equals, 0)
		c.Expect(dayCount, Equals, 0)

		worker.process(message)

		count, _ = redis.Int(conn.Do("get", "stat:processed"))
		dayCount, _ = redis.Int(conn.Do("get", "stat:processed:"+time.Now().UTC().Format(layout)))

		c.Expect(count, Equals, 1)
		c.Expect(dayCount, Equals, 1)
	})

	c.Specify("failed job", func() {
		var job = (func(args *Args) {
			panic("AHHHH")
		})

		manager := newManager("myqueue", job, 1)
		worker := newWorker(manager)

		c.Specify("increments failed stats", func() {
			conn := Config.Pool.Get()
			defer conn.Close()

			count, _ := redis.Int(conn.Do("get", "stat:failed"))
			dayCount, _ := redis.Int(conn.Do("get", "stat:failed:"+time.Now().UTC().Format(layout)))

			c.Expect(count, Equals, 0)
			c.Expect(dayCount, Equals, 0)

			worker.process(message)

			count, _ = redis.Int(conn.Do("get", "stat:failed"))
			dayCount, _ = redis.Int(conn.Do("get", "stat:failed:"+time.Now().UTC().Format(layout)))

			c.Expect(count, Equals, 1)
			c.Expect(dayCount, Equals, 1)
		})
	})
}
예제 #4
0
func ManagerSpec(c gospec.Context) {
	processed := make(chan *Args)

	testJob := (func(args *Args) {
		processed <- args
	})

	c.Specify("newManager", func() {
		c.Specify("sets queue with namespace", func() {
			manager := newManager("myqueue", testJob, 10)
			c.Expect(manager.queue, Equals, "queue:myqueue")
		})

		c.Specify("sets job function", func() {
			manager := newManager("myqueue", testJob, 10)
			c.Expect(fmt.Sprint(manager.job), Equals, fmt.Sprint(testJob))
		})

		c.Specify("sets worker concurrency", func() {
			manager := newManager("myqueue", testJob, 10)
			c.Expect(manager.concurrency, Equals, 10)
		})
	})

	c.Specify("manage", func() {
		conn := Config.pool.Get()
		defer conn.Close()

		message, _ := NewMsg("{\"foo\":\"bar\",\"args\":[\"foo\",\"bar\"]}")
		message2, _ := NewMsg("{\"foo\":\"bar2\",\"args\":[\"foo\",\"bar2\"]}")

		c.Specify("coordinates processing of queue messages", func() {
			manager := newManager("manager1", testJob, 10)

			conn.Do("lpush", "queue:manager1", message.ToJson())
			conn.Do("lpush", "queue:manager1", message2.ToJson())

			manager.start()

			c.Expect(<-processed, Equals, message.Args())
			c.Expect(<-processed, Equals, message2.Args())

			manager.quit()

			len, _ := redis.Int(conn.Do("llen", "queue:manager1"))
			c.Expect(len, Equals, 0)
		})

		c.Specify("prepare stops fetching new messages from queue", func() {
			manager := newManager("manager2", testJob, 10)
			manager.start()

			manager.prepare()

			conn.Do("lpush", "queue:manager2", message)
			conn.Do("lpush", "queue:manager2", message2)

			manager.quit()

			len, _ := redis.Int(conn.Do("llen", "queue:manager2"))
			c.Expect(len, Equals, 2)
		})
	})
}
예제 #5
0
func ConnectionSpec(c gospec.Context) {
	config := NewConfig("localhost:6379", "15", 2)
	config.AddQueue("myqueue", ".*")
	conn := NewConnection(config)

	c.Specify("NewConnection", func() {
		c.Specify("registers any queues defined in configuration", func() {
			c.Expect(len(conn.Queues()), Equals, 1)
			config.AddQueue("myqueue2", ".*")
			conn.RegisterQueues()
			c.Expect(len(conn.Queues()), Equals, 2)
		})

		c.Specify("stores registered queues in redis", func() {
			r := config.Pool.Get()
			defer r.Close()

			values, _ := redis.Strings(r.Do("hgetall", "fairway:registered_queues"))

			expected := []string{"myqueue", ".*"}

			for i, str := range values {
				c.Expect(str, Equals, expected[i])
			}
		})
	})

	c.Specify("Queues", func() {
		c.Specify("returns a Queue for every currently registered queue", func() {
			c.Expect(*conn.Queues()[0], Equals, *NewQueue(conn, "myqueue"))
		})
	})

	c.Specify("Deliver", func() {
		c.Specify("adds message to the facet for the queue", func() {
			r := config.Pool.Get()
			defer r.Close()

			count, _ := redis.Int(r.Do("llen", "fairway:myqueue:default"))
			c.Expect(count, Equals, 0)

			msg, _ := NewMsg(map[string]string{"name": "mymessage"})

			conn.Deliver(msg)

			count, _ = redis.Int(r.Do("llen", "fairway:myqueue:default"))
			c.Expect(count, Equals, 1)

			value, _ := redis.String(r.Do("lindex", "fairway:myqueue:default", 0))
			c.Expect(value, Equals, msg.json())
		})

		c.Specify("adds facets to the list of active facets", func() {
			r := config.Pool.Get()
			defer r.Close()

			facets, _ := redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(facets), Equals, 0)

			msg, _ := NewMsg(map[string]string{})

			conn.Deliver(msg)

			facets, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(facets), Equals, 1)
			c.Expect(facets[0], Equals, "default")
		})

		c.Specify("pushes facet onto the facet queue", func() {
			r := config.Pool.Get()
			defer r.Close()

			count, _ := redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(count, Equals, 0)

			msg, _ := NewMsg(map[string]string{})

			conn.Deliver(msg)

			count, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(count, Equals, 1)

			value, _ := redis.String(r.Do("lindex", "fairway:myqueue:facet_queue", 0))
			c.Expect(value, Equals, "default")
		})

		c.Specify("doesn't push facet if already active", func() {
			r := config.Pool.Get()
			defer r.Close()

			r.Do("sadd", "fairway:myqueue:active_facets", "default")
			r.Do("hset", "fairway:myqueue:facet_pool", "default", "1")

			msg, _ := NewMsg(map[string]string{})

			conn.Deliver(msg)

			count, _ := redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(count, Equals, 0)
		})

		c.Specify("returns nil if delivery succeeds", func() {
			msg, _ := NewMsg(map[string]string{})
			err := conn.Deliver(msg)
			c.Expect(err, IsNil)
		})

		c.Specify("returns error if delivery fails", func() {
			config := NewConfig("localhost:9999", "15", 2)
			conn := NewConnection(config)

			msg, _ := NewMsg(map[string]string{})
			err := conn.Deliver(msg)
			c.Expect(err.Error(), Equals, "dial tcp 127.0.0.1:9999: connection refused")
		})
	})
}
예제 #6
0
func WorkerSpec(c gospec.Context) {
	var processed = make(chan *Args)
	middlewareCalled = false

	var testJob = (func(args *Args) {
		processed <- args
	})

	manager := newManager("myqueue", testJob, 1)

	c.Specify("newWorker", func() {
		c.Specify("it returns an instance of worker with connection to manager", func() {
			worker := newWorker(manager)
			c.Expect(worker.manager, Equals, manager)
		})
	})

	c.Specify("work", func() {
		worker := newWorker(manager)
		messages := make(chan *Msg)
		message, _ := NewMsg("{\"jid\":\"2309823\",\"args\":[\"foo\",\"bar\"]}")

		c.Specify("calls job with message args", func() {
			go worker.work(messages)
			messages <- message

			args, _ := (<-processed).Array()
			<-manager.confirm

			c.Expect(len(args), Equals, 2)
			c.Expect(args[0], Equals, "foo")
			c.Expect(args[1], Equals, "bar")

			worker.quit()
		})

		c.Specify("confirms job completed", func() {
			go worker.work(messages)
			messages <- message

			<-processed
			c.Expect(<-manager.confirm, Equals, message)

			worker.quit()
		})

		c.Specify("runs defined middleware", func() {
			Middleware.Append(&testMiddleware{})

			go worker.work(messages)
			messages <- message

			<-processed
			<-manager.confirm

			c.Expect(middlewareCalled, IsTrue)

			worker.quit()
		})

		c.Specify("recovers and confirms if job panics", func() {
			var panicJob = (func(args *Args) {
				panic("AHHHHHHHHH")
			})

			manager := newManager("myqueue", panicJob, 1)
			worker := newWorker(manager)

			go worker.work(messages)
			messages <- message
			c.Expect(<-manager.confirm, Equals, message)

			worker.quit()
		})
	})
}
func ChanneledConnectionSpec(c gospec.Context) {
	config := NewConfig("localhost:6379", "15", 2)
	config.AddQueue("myqueue", "typea")
	config.AddQueue("myqueue2", "typeb")

	conn := NewChanneledConnection(config, func(message *Msg) string {
		channel, _ := message.Get("type").String()
		return fmt.Sprint("channel:type", channel, ":channel")
	})

	c.Specify("Deliver", func() {
		c.Specify("only queues up message for matching queues", func() {
			r := config.Pool.Get()
			defer r.Close()

			count, _ := redis.Int(r.Do("llen", "fairway:myqueue:default"))
			c.Expect(count, Equals, 0)
			count, _ = redis.Int(r.Do("llen", "fairway:myqueue2:default"))
			c.Expect(count, Equals, 0)

			msg, _ := NewMsg(map[string]string{"type": "a"})

			conn.Deliver(msg)

			count, _ = redis.Int(r.Do("llen", "fairway:myqueue:default"))
			c.Expect(count, Equals, 1)
			count, _ = redis.Int(r.Do("llen", "fairway:myqueue2:default"))
			c.Expect(count, Equals, 0)
		})
	})

	c.Specify("DeliverBytes", func() {
		c.Specify("only queues up message for matching queues", func() {
			r := config.Pool.Get()
			defer r.Close()

			count, _ := redis.Int(r.Do("llen", "fairway:myqueue:default"))
			c.Expect(count, Equals, 0)
			count, _ = redis.Int(r.Do("llen", "fairway:myqueue2:default"))
			c.Expect(count, Equals, 0)

			msg, _ := NewMsg(map[string]string{"type": "a"})

			conn.DeliverBytes("channel:typea:channel", "default", []byte(msg.json()))

			count, _ = redis.Int(r.Do("llen", "fairway:myqueue:default"))
			c.Expect(count, Equals, 1)
			count, _ = redis.Int(r.Do("llen", "fairway:myqueue2:default"))
			c.Expect(count, Equals, 0)
		})
	})
}
예제 #8
0
func WorkerSpec(c gospec.Context) {
	var processed = make(chan *Args)

	var testJob = (func(message *Msg) {
		processed <- message.Args()
	})

	manager := newManager("myqueue", testJob, 1)

	c.Specify("newWorker", func() {
		c.Specify("it returns an instance of worker with connection to manager", func() {
			worker := newWorker(manager)
			c.Expect(worker.manager, Equals, manager)
		})
	})

	c.Specify("work", func() {
		worker := newWorker(manager)
		messages := make(chan *Msg)
		message, _ := NewMsg("{\"jid\":\"2309823\",\"args\":[\"foo\",\"bar\"]}")

		c.Specify("calls job with message args", func() {
			go worker.work(messages)
			messages <- message

			args, _ := (<-processed).Array()
			<-manager.confirm

			c.Expect(len(args), Equals, 2)
			c.Expect(args[0], Equals, "foo")
			c.Expect(args[1], Equals, "bar")

			worker.quit()
		})

		c.Specify("confirms job completed", func() {
			go worker.work(messages)
			messages <- message

			<-processed

			c.Expect(confirm(manager), Equals, message)

			worker.quit()
		})

		c.Specify("runs defined middleware and confirms", func() {
			Middleware.Append(&testMiddleware{})

			go worker.work(messages)
			messages <- message

			<-processed
			c.Expect(confirm(manager), Equals, message)
			c.Expect(testMiddlewareCalled, IsTrue)

			worker.quit()

			Middleware = NewMiddleware(
				&MiddlewareLogging{},
				&MiddlewareRetry{},
				&MiddlewareStats{},
			)
		})

		c.Specify("doesn't confirm if middleware cancels acknowledgement", func() {
			Middleware.Append(&failMiddleware{})

			go worker.work(messages)
			messages <- message

			<-processed
			c.Expect(confirm(manager), IsNil)
			c.Expect(failMiddlewareCalled, IsTrue)

			worker.quit()

			Middleware = NewMiddleware(
				&MiddlewareLogging{},
				&MiddlewareRetry{},
				&MiddlewareStats{},
			)
		})

		c.Specify("recovers and confirms if job panics", func() {
			var panicJob = (func(message *Msg) {
				panic("AHHHHHHHHH")
			})

			manager := newManager("myqueue", panicJob, 1)
			worker := newWorker(manager)

			go worker.work(messages)
			messages <- message

			c.Expect(confirm(manager), Equals, message)

			worker.quit()
		})
	})
}
예제 #9
0
func EnqueueSpec(c gospec.Context) {
	was := Config.Namespace
	Config.Namespace = "prod:"

	c.Specify("Enqueue", func() {
		conn := Config.Pool.Get()
		defer conn.Close()

		c.Specify("makes the queue available", func() {
			Enqueue("enqueue1", "Add", []int{1, 2})

			found, _ := redis.Bool(conn.Do("sismember", "prod:queues", "enqueue1"))
			c.Expect(found, IsTrue)
		})

		c.Specify("adds a job to the queue", func() {
			nb, _ := redis.Int(conn.Do("llen", "prod:queue:enqueue2"))
			c.Expect(nb, Equals, 0)

			Enqueue("enqueue2", "Add", []int{1, 2})

			nb, _ = redis.Int(conn.Do("llen", "prod:queue:enqueue2"))
			c.Expect(nb, Equals, 1)
		})

		c.Specify("saves the arguments", func() {
			Enqueue("enqueue3", "Compare", []string{"foo", "bar"})

			bytes, _ := redis.Bytes(conn.Do("lpop", "prod:queue:enqueue3"))
			var result map[string]interface{}
			json.Unmarshal(bytes, &result)
			c.Expect(result["class"], Equals, "Compare")

			args := result["args"].([]interface{})
			c.Expect(len(args), Equals, 2)
			c.Expect(args[0], Equals, "foo")
			c.Expect(args[1], Equals, "bar")
		})

		c.Specify("has a jid", func() {
			Enqueue("enqueue4", "Compare", []string{"foo", "bar"})

			bytes, _ := redis.Bytes(conn.Do("lpop", "prod:queue:enqueue4"))
			var result map[string]interface{}
			json.Unmarshal(bytes, &result)
			c.Expect(result["class"], Equals, "Compare")

			jid := result["jid"].(string)
			c.Expect(len(jid), Equals, 24)
		})

		c.Specify("has enqueued_at that is close to now", func() {
			Enqueue("enqueue5", "Compare", []string{"foo", "bar"})

			bytes, _ := redis.Bytes(conn.Do("lpop", "prod:queue:enqueue5"))
			var result map[string]interface{}
			json.Unmarshal(bytes, &result)
			c.Expect(result["class"], Equals, "Compare")

			ea := result["enqueued_at"].(float64)
			c.Expect(ea, Not(Equals), 0)
			c.Expect(ea, IsWithin(0.1), nowToSecondsWithNanoPrecision())
		})

		c.Specify("has retry and retry_count when set", func() {
			EnqueueWithOptions("enqueue6", "Compare", []string{"foo", "bar"}, EnqueueOptions{RetryCount: 1, Retry: 3})

			bytes, _ := redis.Bytes(conn.Do("lpop", "prod:queue:enqueue6"))
			var result map[string]interface{}
			json.Unmarshal(bytes, &result)
			c.Expect(result["class"], Equals, "Compare")

			retry := result["retry"].(int)
			c.Expect(retry, Equals, true)

			retryCount := int(result["retry_count"].(float64))
			c.Expect(retryCount, Equals, 13)
		})
	})

	c.Specify("EnqueueIn", func() {
		scheduleQueue := "prod:" + SCHEDULED_JOBS_KEY
		conn := Config.Pool.Get()
		defer conn.Close()

		c.Specify("has added a job in the scheduled queue", func() {
			_, err := EnqueueIn("enqueuein1", "Compare", 10, map[string]interface{}{"foo": "bar"})
			c.Expect(err, Equals, nil)

			scheduledCount, _ := redis.Int(conn.Do("zcard", scheduleQueue))
			c.Expect(scheduledCount, Equals, 1)

			conn.Do("del", scheduleQueue)
		})

		c.Specify("has the correct 'queue'", func() {
			_, err := EnqueueIn("enqueuein2", "Compare", 10, map[string]interface{}{"foo": "bar"})
			c.Expect(err, Equals, nil)

			var data EnqueueData
			elem, err := conn.Do("zrange", scheduleQueue, 0, -1)
			bytes, err := redis.Bytes(elem.([]interface{})[0], err)
			json.Unmarshal(bytes, &data)

			c.Expect(data.Queue, Equals, "enqueuein2")

			conn.Do("del", scheduleQueue)
		})
	})

	Config.Namespace = was
}
예제 #10
0
func EnqueueSpec(c gospec.Context) {
	was := Config.namespace
	Config.namespace = "prod:"

	c.Specify("Enqueue", func() {
		conn := Config.Pool.Get()
		defer conn.Close()

		c.Specify("makes the queue available", func() {
			Enqueue("enqueue1", "Add", []int{1, 2})

			found, _ := redis.Bool(conn.Do("sismember", "prod:queues", "enqueue1"))
			c.Expect(found, IsTrue)
		})

		c.Specify("adds a job to the queue", func() {
			nb, _ := redis.Int(conn.Do("llen", "prod:queue:enqueue2"))
			c.Expect(nb, Equals, 0)

			Enqueue("enqueue2", "Add", []int{1, 2})

			nb, _ = redis.Int(conn.Do("llen", "prod:queue:enqueue2"))
			c.Expect(nb, Equals, 1)
		})

		c.Specify("saves the arguments", func() {
			Enqueue("enqueue3", "Compare", []string{"foo", "bar"})

			bytes, _ := redis.Bytes(conn.Do("lpop", "prod:queue:enqueue3"))
			var result map[string]interface{}
			json.Unmarshal(bytes, &result)
			c.Expect(result["class"], Equals, "Compare")

			args := result["args"].([]interface{})
			c.Expect(len(args), Equals, 2)
			c.Expect(args[0], Equals, "foo")
			c.Expect(args[1], Equals, "bar")
		})
	})

	Config.namespace = was
}
예제 #11
0
func QueueSpec(c gospec.Context) {
	config := NewConfig("localhost:6379", "15", 2)
	config.AddQueue("myqueue", ".*")
	conn := NewConnection(config)
	queue := NewQueue(conn, "myqueue")

	c.Specify("NewQueue", func() {
	})

	c.Specify("Pull", func() {
		c.Specify("pulls a message off the queue using FIFO", func() {
			msg1, _ := NewMsg(map[string]string{"name": "mymessage1"})
			msg2, _ := NewMsg(map[string]string{"name": "mymessage2"})

			conn.Deliver(msg1)
			conn.Deliver(msg2)

			queueName, message := queue.Pull(-1)
			c.Expect(queueName, Equals, "myqueue")
			c.Expect(message.json(), Equals, msg1.json())

			queueName, message = queue.Pull(-1)
			c.Expect(queueName, Equals, "myqueue")
			c.Expect(message.json(), Equals, msg2.json())
		})

		c.Specify("places pulled message on inflight sorted set until acknowledged", func() {
			msg1, _ := NewMsg(map[string]string{"name": "mymessage1"})

			conn.Deliver(msg1)

			c.Expect(len(queue.Inflight()), Equals, 0)

			queueName, message := queue.Pull(100)
			c.Expect(queueName, Equals, "myqueue")
			c.Expect(message.json(), Equals, msg1.json())

			c.Expect(len(queue.Inflight()), Equals, 1)
			c.Expect(queue.Inflight()[0], Equals, msg1.json())

			queue.Ack(msg1)

			c.Expect(len(queue.Inflight()), Equals, 0)
		})

		c.Specify("pulls from inflight message set if messages are unacknowledged", func() {
			msg1, _ := NewMsg(map[string]string{"name": "mymessage1"})
			msg2, _ := NewMsg(map[string]string{"name": "mymessage2"})

			conn.Deliver(msg1)
			conn.Deliver(msg2)

			queueName, message := queue.Pull(0)
			c.Expect(queueName, Equals, "myqueue")
			c.Expect(message.json(), Equals, msg1.json())

			queueName, message = queue.Pull(10)
			c.Expect(queueName, Equals, "myqueue")
			c.Expect(message.json(), Equals, msg1.json())

			queueName, message = queue.Pull(10)
			c.Expect(queueName, Equals, "myqueue")
			c.Expect(message.json(), Equals, msg2.json())
		})

		c.Specify("allows puller to ping to keep message inflight", func() {
			msg1, _ := NewMsg(map[string]string{"name": "mymessage1"})
			msg2, _ := NewMsg(map[string]string{"name": "mymessage2"})

			conn.Deliver(msg1)
			conn.Deliver(msg2)

			queueName, message := queue.Pull(0)
			c.Expect(queueName, Equals, "myqueue")
			c.Expect(message.json(), Equals, msg1.json())

			// Extends time before message is resent
			queue.Ping(msg1, 10)

			queueName, message = queue.Pull(10)
			c.Expect(queueName, Equals, "myqueue")
			c.Expect(message.json(), Equals, msg2.json())

			// Sets time for message to resend to now
			queue.Ping(msg1, 0)

			queueName, message = queue.Pull(10)
			c.Expect(queueName, Equals, "myqueue")
			c.Expect(message.json(), Equals, msg1.json())
		})

		c.Specify("set limits messages inflight", func() {
			limit, err := queue.InflightLimit()

			c.Expect(limit, Equals, 0)
			c.Expect(err, IsNil)

			queue.SetInflightLimit(1)

			limit, err = queue.InflightLimit()

			c.Expect(limit, Equals, 1)
			c.Expect(err, IsNil)
		})

		c.Specify("limits messages inflight", func() {
			r := config.Pool.Get()
			defer r.Close()

			config.Facet = func(msg *Msg) string {
				str, _ := msg.Get("facet").String()
				return str
			}

			msg1, _ := NewMsg(map[string]string{"facet": "1", "name": "mymessage1"})
			msg2, _ := NewMsg(map[string]string{"facet": "1", "name": "mymessage2"})
			msg3, _ := NewMsg(map[string]string{"facet": "2", "name": "mymessage3"})

			conn.Deliver(msg1)
			conn.Deliver(msg2)
			conn.Deliver(msg3)

			queue.SetInflightLimit(1)

			_, message := queue.Pull(2)
			c.Expect(message.json(), Equals, msg1.json())

			count, _ := redis.Int(r.Do("get", "fairway:myqueue:1:inflight"))
			c.Expect(count, Equals, 1)

			_, message = queue.Pull(2)
			c.Expect(message.json(), Equals, msg3.json())

			count, _ = redis.Int(r.Do("get", "fairway:myqueue:1:inflight"))
			c.Expect(count, Equals, 1)

			count, _ = redis.Int(r.Do("get", "fairway:myqueue:2:inflight"))
			c.Expect(count, Equals, 1)

			_, message = queue.Pull(2)
			c.Expect(message, IsNil)
			_, message = queue.Pull(2)
			c.Expect(message, IsNil)

			count, _ = redis.Int(r.Do("get", "fairway:myqueue:1:inflight"))
			c.Expect(count, Equals, 1)

			count, _ = redis.Int(r.Do("get", "fairway:myqueue:2:inflight"))
			c.Expect(count, Equals, 1)

			queue.Ack(msg1)
			queue.Ack(msg1)
			queue.Ack(msg1)
			queue.Ack(msg1)
			queue.Ack(msg1)

			count, err := redis.Int(r.Do("get", "fairway:myqueue:1:inflight"))
			c.Expect(count, Equals, 0)
			c.Expect(err.Error(), Equals, "redigo: nil returned")

			count, _ = redis.Int(r.Do("get", "fairway:myqueue:2:inflight"))
			c.Expect(count, Equals, 1)

			_, message = queue.Pull(2)
			c.Expect(message.json(), Equals, msg2.json())

			count, _ = redis.Int(r.Do("get", "fairway:myqueue:1:inflight"))
			c.Expect(count, Equals, 1)

			count, _ = redis.Int(r.Do("get", "fairway:myqueue:2:inflight"))
			c.Expect(count, Equals, 1)
		})

		c.Specify("prevents overlimit messages when all messages are inflight", func() {
			r := config.Pool.Get()
			defer r.Close()

			config.Facet = func(msg *Msg) string {
				str, _ := msg.Get("facet").String()
				return str
			}

			msg1, _ := NewMsg(map[string]string{"facet": "1", "name": "mymessage1"})
			msg2, _ := NewMsg(map[string]string{"facet": "1", "name": "mymessage2"})
			msg3, _ := NewMsg(map[string]string{"facet": "1", "name": "mymessage3"})

			queue.SetInflightLimit(1)

			active, _ := redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 0)
			fqueue, _ := redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 0)

			conn.Deliver(msg1)

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			c.Expect(active[0], Equals, "1")
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 1)

			_, message := queue.Pull(2)
			c.Expect(message.json(), Equals, msg1.json())

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			c.Expect(active[0], Equals, "1")
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 0)

			conn.Deliver(msg2)

			_, message = queue.Pull(2)
			c.Expect(message, IsNil)

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			c.Expect(active[0], Equals, "1")
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 0)

			queue.Ack(msg1)

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			c.Expect(active[0], Equals, "1")
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 1)

			_, message = queue.Pull(2)
			c.Expect(message.json(), Equals, msg2.json())

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			c.Expect(active[0], Equals, "1")
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 0)

			conn.Deliver(msg3)

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			c.Expect(active[0], Equals, "1")
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 0)

			_, message = queue.Pull(2)
			c.Expect(message, IsNil)

			queue.Ack(msg2)

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			c.Expect(active[0], Equals, "1")
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 1)

			_, message = queue.Pull(2)
			c.Expect(message.json(), Equals, msg3.json())

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			c.Expect(active[0], Equals, "1")
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 0)

			_, message = queue.Pull(2)
			c.Expect(message, IsNil)

			queue.Ack(msg3)

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 0)
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 0)

			msg4, _ := NewMsg(map[string]string{"facet": "1", "name": "mymessage4"})

			conn.Deliver(msg4)

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 1)

			_, message = queue.Pull(2)
			c.Expect(message.json(), Equals, msg4.json())

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 0)

			queue.Ack(msg4)

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 0)
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 0)

			_, message = queue.Pull(2)
			c.Expect(message, IsNil)

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 0)
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 0)
		})

		c.Specify("if inflight limit is 0, no limit", func() {
			r := config.Pool.Get()
			defer r.Close()

			config.Facet = func(msg *Msg) string {
				str, _ := msg.Get("facet").String()
				return str
			}

			msg1, _ := NewMsg(map[string]string{"facet": "1", "name": "mymessage1"})
			msg2, _ := NewMsg(map[string]string{"facet": "1", "name": "mymessage2"})
			msg3, _ := NewMsg(map[string]string{"facet": "2", "name": "mymessage3"})

			queue.SetInflightLimit(0)

			conn.Deliver(msg1)
			conn.Deliver(msg2)
			conn.Deliver(msg3)

			active, _ := redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 2)
			fqueue, _ := redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 2)

			_, message := queue.Pull(2)
			c.Expect(message.json(), Equals, msg1.json())

			_, message = queue.Pull(2)
			c.Expect(message.json(), Equals, msg3.json())

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 1)

			_, message = queue.Pull(2)
			c.Expect(message.json(), Equals, msg2.json())

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 0)
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 0)

			msg4, _ := NewMsg(map[string]string{"facet": "2", "name": "mymessage4"})

			conn.Deliver(msg4)

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 1)

			queue.Ack(msg1)

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 1)

			_, message = queue.Pull(2)
			c.Expect(message.json(), Equals, msg4.json())

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 0)
			fqueue, _ = redis.Int(r.Do("llen", "fairway:myqueue:facet_queue"))
			c.Expect(fqueue, Equals, 0)

			_, message = queue.Pull(2)
			c.Expect(message, IsNil)

			queue.Ack(msg2)
			queue.Ack(msg3)
			queue.Ack(msg4)
		})

		c.Specify("doesn't place pulled message on inflight sorted set if inflight is disabled", func() {
			msg1, _ := NewMsg(map[string]string{"name": "mymessage1"})

			conn.Deliver(msg1)

			c.Expect(len(queue.Inflight()), Equals, 0)

			queueName, message := queue.Pull(-1)
			c.Expect(queueName, Equals, "myqueue")
			c.Expect(message.json(), Equals, msg1.json())

			c.Expect(len(queue.Inflight()), Equals, 0)
		})

		c.Specify("doesn't pull from inflight message set if inflight is disabled", func() {
			msg1, _ := NewMsg(map[string]string{"name": "mymessage1"})
			msg2, _ := NewMsg(map[string]string{"name": "mymessage2"})

			conn.Deliver(msg1)
			conn.Deliver(msg2)

			queueName, message := queue.Pull(-1)
			c.Expect(queueName, Equals, "myqueue")
			c.Expect(message.json(), Equals, msg1.json())

			queueName, message = queue.Pull(-1)
			c.Expect(queueName, Equals, "myqueue")
			c.Expect(message.json(), Equals, msg2.json())
		})

		c.Specify("pulls from facets of the queue in round-robin", func() {
			r := config.Pool.Get()
			defer r.Close()

			config.Facet = func(msg *Msg) string {
				str, _ := msg.Get("facet").String()
				return str
			}

			msg1, _ := NewMsg(map[string]string{"facet": "1", "name": "mymessage1"})
			msg2, _ := NewMsg(map[string]string{"facet": "1", "name": "mymessage2"})
			msg3, _ := NewMsg(map[string]string{"facet": "2", "name": "mymessage3"})

			active, _ := redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 0)

			conn.Deliver(msg1)
			conn.Deliver(msg2)

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			c.Expect(active[0], Equals, "1")

			conn.Deliver(msg3)

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 2)
			c.Expect(active[0], Equals, "1")
			c.Expect(active[1], Equals, "2")

			_, message := queue.Pull(-1)
			c.Expect(message.json(), Equals, msg1.json())

			_, message = queue.Pull(-1)
			c.Expect(message.json(), Equals, msg3.json())

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 1)
			c.Expect(active[0], Equals, "1")

			_, message = queue.Pull(-1)
			c.Expect(message.json(), Equals, msg2.json())

			active, _ = redis.Strings(r.Do("smembers", "fairway:myqueue:active_facets"))
			c.Expect(len(active), Equals, 0)

			_, message = queue.Pull(2)
			c.Expect(message, IsNil)
		})

		c.Specify("removes facet from active list if it becomes empty", func() {
			r := config.Pool.Get()
			defer r.Close()

			msg, _ := NewMsg(map[string]string{})
			conn.Deliver(msg)

			count, _ := redis.Int(r.Do("scard", "fairway:myqueue:active_facets"))
			c.Expect(count, Equals, 1)

			queue.Pull(-1)

			count, _ = redis.Int(r.Do("scard", "fairway:myqueue:active_facets"))
			c.Expect(count, Equals, 0)
		})

		c.Specify("returns nil if there are no messages to receive", func() {
			msg, _ := NewMsg(map[string]string{})
			conn.Deliver(msg)

			queueName, message := queue.Pull(-1)
			c.Expect(queueName, Equals, "myqueue")
			queueName, message = queue.Pull(-1)
			c.Expect(queueName, Equals, "")
			c.Expect(message, IsNil)
		})
	})
}
예제 #12
0
func MiddlewareSpec(c gospec.Context) {
	middleware := NewMiddleware()
	message, _ := NewMsg("{\"foo\":\"bar\"}")
	order = make([]string, 0)
	first := &m1{}
	second := &m2{}

	c.Specify("newMiddleware", func() {
		c.Specify("doesn't contain any middleware", func() {
			c.Expect(len(middleware.actions), Equals, 0)
		})

		c.Specify("can specify middleware when initializing", func() {
			middleware = NewMiddleware(first, second)
			c.Expect(middleware.actions[0], Equals, first)
			c.Expect(middleware.actions[1], Equals, second)
		})
	})

	c.Specify("Append", func() {
		c.Specify("adds function at the end of the list", func() {
			middleware.Append(first)
			middleware.Append(second)

			middleware.call("myqueue", message, func() {
				order = append(order, "job")
			})

			c.Expect(
				arrayCompare(order, []string{
					"m1 enter",
					"m2 enter",
					"job",
					"m2 leave",
					"m1 leave",
				}),
				IsTrue,
			)
		})
	})

	c.Specify("{repend", func() {
		c.Specify("adds function at the beginning of the list", func() {
			middleware.Prepend(first)
			middleware.Prepend(second)

			middleware.call("myqueue", message, func() {
				order = append(order, "job")
			})

			c.Expect(
				arrayCompare(order, []string{
					"m2 enter",
					"m1 enter",
					"job",
					"m1 leave",
					"m2 leave",
				}),
				IsTrue,
			)
		})
	})
}
예제 #13
0
func ConfigSpec(c gospec.Context) {
	config := NewConfig("localhost:6379", "15", 10)

	c.Specify("NewConfig", func() {
		c.Specify("namespace is fairway", func() {
			c.Expect(config.Namespace, Equals, "fairway")
		})

		c.Specify("sets the facet to always return 'default'", func() {
			msg, _ := NewMsg(make([]string, 0))
			c.Expect(config.Facet(msg), Equals, "default")
		})

		c.Specify("doesn't have any defined queues", func() {
			c.Expect(len(config.queues), Equals, 0)
		})
	})

	c.Specify("sets redis pool size", func() {
		c.Expect(config.Pool.MaxIdle, Equals, 10)
		c.Expect(config.Pool.MaxActive, Equals, 0)
		config = NewConfig("localhost:6379", "15", 20)
		c.Expect(config.Pool.MaxIdle, Equals, 20)
		c.Expect(config.Pool.MaxActive, Equals, 0)
	})

	c.Specify("can specify custom namespace", func() {
		config.Namespace = "mynamespace"
		c.Expect(config.Namespace, Equals, "mynamespace")
	})

	c.Specify("can specify custom facet", func() {
		config.Facet = func(message *Msg) string {
			return "myfacet"
		}
		msg, _ := NewMsg(make([]string, 0))
		c.Expect(config.Facet(msg), Equals, "myfacet")
	})

	c.Specify("can define a queue", func() {
		config.AddQueue("myqueue", "default")
		c.Expect(len(config.queues), Equals, 1)
		c.Expect(*config.queues[0], Equals, QueueDefinition{"myqueue", "default"})
	})
}
예제 #14
0
func MsgSpec(c gospec.Context) {
	c.Specify("NewMsg", func() {
		c.Specify("returns a new message with body as the content", func() {
			msg, _ := NewMsg(map[string]string{"hello": "world"})
			c.Expect(msg.json(), Equals, "{\"hello\":\"world\"}")
		})

		c.Specify("returns err if couldn't convert object", func() {
			msg, err := NewMsg(func() {})
			c.Expect(msg, IsNil)
			c.Expect(err, Not(IsNil))
		})
	})

	c.Specify("NewMsgFromString", func() {
		c.Specify("returns a new message with string as the content", func() {
			msg, _ := NewMsgFromString("{\"hello\":\"world\"}")
			c.Expect(msg.json(), Equals, "{\"hello\":\"world\"}")
		})

		c.Specify("returns err if couldn't convert string", func() {
			msg, err := NewMsgFromString("not json")
			c.Expect(msg, IsNil)
			c.Expect(err, Not(IsNil))
		})
	})
}
예제 #15
0
func MiddlewareRetrySpec(c gospec.Context) {
	var panicingJob = (func(args *Args) {
		panic("AHHHH")
	})

	var wares = newMiddleware(
		&MiddlewareRetry{},
	)

	layout := "2006-01-02 15:04:05 MST"
	manager := newManager("myqueue", panicingJob, 1)
	worker := newWorker(manager)

	c.Specify("puts messages in retry queue when they fail", func() {
		message, _ := NewMsg("{\"jid\":\"2\",\"retry\":true}")

		wares.call("myqueue", message, func() {
			worker.process(message)
		})

		conn := Config.Pool.Get()
		defer conn.Close()

		retries, _ := redis.Strings(conn.Do("zrange", RETRY_KEY, 0, 1))
		c.Expect(retries[0], Equals, message.ToJson())
	})

	c.Specify("allows disabling retries", func() {
		message, _ := NewMsg("{\"jid\":\"2\",\"retry\":false}")

		wares.call("myqueue", message, func() {
			worker.process(message)
		})

		conn := Config.Pool.Get()
		defer conn.Close()

		count, _ := redis.Int(conn.Do("zcard", RETRY_KEY))
		c.Expect(count, Equals, 0)
	})

	c.Specify("doesn't retry by default", func() {
		message, _ := NewMsg("{\"jid\":\"2\"}")

		wares.call("myqueue", message, func() {
			worker.process(message)
		})

		conn := Config.Pool.Get()
		defer conn.Close()

		count, _ := redis.Int(conn.Do("zcard", RETRY_KEY))
		c.Expect(count, Equals, 0)
	})

	c.Specify("allows numeric retries", func() {
		message, _ := NewMsg("{\"jid\":\"2\",\"retry\":5}")

		wares.call("myqueue", message, func() {
			worker.process(message)
		})

		conn := Config.Pool.Get()
		defer conn.Close()

		retries, _ := redis.Strings(conn.Do("zrange", RETRY_KEY, 0, 1))
		c.Expect(retries[0], Equals, message.ToJson())
	})

	c.Specify("handles new failed message", func() {
		message, _ := NewMsg("{\"jid\":\"2\",\"retry\":true}")

		wares.call("myqueue", message, func() {
			worker.process(message)
		})

		conn := Config.Pool.Get()
		defer conn.Close()

		retries, _ := redis.Strings(conn.Do("zrange", RETRY_KEY, 0, 1))
		message, _ = NewMsg(retries[0])

		queue, _ := message.Get("queue").String()
		error_message, _ := message.Get("error_message").String()
		error_class, _ := message.Get("error_class").String()
		retry_count, _ := message.Get("retry_count").Int()
		error_backtrace, _ := message.Get("error_backtrace").String()
		failed_at, _ := message.Get("failed_at").String()

		c.Expect(queue, Equals, "myqueue")
		c.Expect(error_message, Equals, "AHHHH")
		c.Expect(error_class, Equals, "")
		c.Expect(retry_count, Equals, 0)
		c.Expect(error_backtrace, Equals, "")
		c.Expect(failed_at, Equals, time.Now().UTC().Format(layout))
	})

	c.Specify("handles recurring failed message", func() {
		message, _ := NewMsg("{\"jid\":\"2\",\"retry\":true,\"queue\":\"default\",\"error_message\":\"bam\",\"failed_at\":\"2013-07-20 14:03:42 UTC\",\"retry_count\":10}")

		wares.call("myqueue", message, func() {
			worker.process(message)
		})

		conn := Config.Pool.Get()
		defer conn.Close()

		retries, _ := redis.Strings(conn.Do("zrange", RETRY_KEY, 0, 1))
		message, _ = NewMsg(retries[0])

		queue, _ := message.Get("queue").String()
		error_message, _ := message.Get("error_message").String()
		retry_count, _ := message.Get("retry_count").Int()
		failed_at, _ := message.Get("failed_at").String()
		retried_at, _ := message.Get("retried_at").String()

		c.Expect(queue, Equals, "myqueue")
		c.Expect(error_message, Equals, "AHHHH")
		c.Expect(retry_count, Equals, 11)
		c.Expect(failed_at, Equals, "2013-07-20 14:03:42 UTC")
		c.Expect(retried_at, Equals, time.Now().UTC().Format(layout))
	})

	c.Specify("handles recurring failed message with customized max", func() {
		message, _ := NewMsg("{\"jid\":\"2\",\"retry\":10,\"queue\":\"default\",\"error_message\":\"bam\",\"failed_at\":\"2013-07-20 14:03:42 UTC\",\"retry_count\":8}")

		wares.call("myqueue", message, func() {
			worker.process(message)
		})

		conn := Config.Pool.Get()
		defer conn.Close()

		retries, _ := redis.Strings(conn.Do("zrange", RETRY_KEY, 0, 1))
		message, _ = NewMsg(retries[0])

		queue, _ := message.Get("queue").String()
		error_message, _ := message.Get("error_message").String()
		retry_count, _ := message.Get("retry_count").Int()
		failed_at, _ := message.Get("failed_at").String()
		retried_at, _ := message.Get("retried_at").String()

		c.Expect(queue, Equals, "myqueue")
		c.Expect(error_message, Equals, "AHHHH")
		c.Expect(retry_count, Equals, 9)
		c.Expect(failed_at, Equals, "2013-07-20 14:03:42 UTC")
		c.Expect(retried_at, Equals, time.Now().UTC().Format(layout))
	})

	c.Specify("doesn't retry after default number of retries", func() {
		message, _ := NewMsg("{\"jid\":\"2\",\"retry\":true,\"retry_count\":25}")

		wares.call("myqueue", message, func() {
			worker.process(message)
		})

		conn := Config.Pool.Get()
		defer conn.Close()

		count, _ := redis.Int(conn.Do("zcard", RETRY_KEY))
		c.Expect(count, Equals, 0)
	})

	c.Specify("doesn't retry after customized number of retries", func() {
		message, _ := NewMsg("{\"jid\":\"2\",\"retry\":3,\"retry_count\":3}")

		wares.call("myqueue", message, func() {
			worker.process(message)
		})

		conn := Config.Pool.Get()
		defer conn.Close()

		count, _ := redis.Int(conn.Do("zcard", RETRY_KEY))
		c.Expect(count, Equals, 0)
	})
}
예제 #16
0
func FetchSpec(c gospec.Context) {
	c.Specify("Config.Fetch", func() {
		c.Specify("it returns an instance of fetch with queue", func() {
			fetch := buildFetch("fetchQueue1")
			c.Expect(fetch.Queue(), Equals, "queue:fetchQueue1")
			fetch.Close()
		})
	})

	c.Specify("Fetch", func() {
		message, _ := NewMsg("{\"foo\":\"bar\"}")

		c.Specify("it puts messages from the queues on the messages channel", func() {
			fetch := buildFetch("fetchQueue2")

			conn := Config.Pool.Get()
			defer conn.Close()

			conn.Do("lpush", "queue:fetchQueue2", message.ToJson())

			message := <-fetch.Messages()

			c.Expect(message, Equals, message)

			len, _ := redis.Int(conn.Do("llen", "queue:fetchQueue2"))
			c.Expect(len, Equals, 0)

			fetch.Close()
		})

		c.Specify("places in progress messages on private queue", func() {
			fetch := buildFetch("fetchQueue3")

			conn := Config.Pool.Get()
			defer conn.Close()

			conn.Do("lpush", "queue:fetchQueue3", message.ToJson())

			<-fetch.Messages()

			len, _ := redis.Int(conn.Do("llen", "queue:fetchQueue3:1:inprogress"))
			c.Expect(len, Equals, 1)

			messages, _ := redis.Strings(conn.Do("lrange", "queue:fetchQueue3:1:inprogress", 0, -1))
			c.Expect(messages[0], Equals, message.ToJson())

			fetch.Close()
		})

		c.Specify("removes in progress message when acknowledged", func() {
			fetch := buildFetch("fetchQueue4")

			conn := Config.Pool.Get()
			defer conn.Close()

			conn.Do("lpush", "queue:fetchQueue4", message.ToJson())

			<-fetch.Messages()

			fetch.Acknowledge(message)

			len, _ := redis.Int(conn.Do("llen", "queue:fetchQueue4:1:inprogress"))
			c.Expect(len, Equals, 0)

			fetch.Close()
		})

		c.Specify("removes in progress message when serialized differently", func() {
			json := "{\"foo\":\"bar\",\"args\":[]}"
			message, _ := NewMsg(json)

			c.Expect(json, Not(Equals), message.ToJson())

			fetch := buildFetch("fetchQueue5")

			conn := Config.Pool.Get()
			defer conn.Close()

			conn.Do("lpush", "queue:fetchQueue5", json)

			<-fetch.Messages()

			fetch.Acknowledge(message)

			len, _ := redis.Int(conn.Do("llen", "queue:fetchQueue5:1:inprogress"))
			c.Expect(len, Equals, 0)

			fetch.Close()
		})

		c.Specify("refires any messages left in progress from prior instance", func() {
			message2, _ := NewMsg("{\"foo\":\"bar2\"}")
			message3, _ := NewMsg("{\"foo\":\"bar3\"}")

			conn := Config.Pool.Get()
			defer conn.Close()

			conn.Do("lpush", "queue:fetchQueue6:1:inprogress", message.ToJson())
			conn.Do("lpush", "queue:fetchQueue6:1:inprogress", message2.ToJson())
			conn.Do("lpush", "queue:fetchQueue6", message3.ToJson())

			fetch := buildFetch("fetchQueue6")

			c.Expect(<-fetch.Messages(), Equals, message2)
			c.Expect(<-fetch.Messages(), Equals, message)
			c.Expect(<-fetch.Messages(), Equals, message3)

			fetch.Acknowledge(message)
			fetch.Acknowledge(message2)
			fetch.Acknowledge(message3)

			len, _ := redis.Int(conn.Do("llen", "queue:fetchQueue6:1:inprogress"))
			c.Expect(len, Equals, 0)

			fetch.Close()
		})
	})
}
예제 #17
0
func WorkersSpec(c gospec.Context) {
	c.Specify("Workers", func() {
		c.Specify("allows running in tests", func() {
			called = make(chan bool)

			Process("myqueue", myJob, 10)

			Start()

			Enqueue("myqueue", "Add", []int{1, 2})
			<-called

			Quit()
		})

		// TODO make this test more deterministic, randomly locks up in travis.
		//c.Specify("allows starting and stopping multiple times", func() {
		//	called = make(chan bool)

		//	Process("myqueue", myJob, 10)

		//	Start()
		//	Quit()

		//	Start()

		//	Enqueue("myqueue", "Add", []int{1, 2})
		//	<-called

		//	Quit()
		//})

		c.Specify("runs beforeStart hooks", func() {
			hooks := []string{}

			BeforeStart(func() {
				hooks = append(hooks, "1")
			})
			BeforeStart(func() {
				hooks = append(hooks, "2")
			})
			BeforeStart(func() {
				hooks = append(hooks, "3")
			})

			Start()

			c.Expect(reflect.DeepEqual(hooks, []string{"1", "2", "3"}), IsTrue)

			Quit()

			// Clear out global hooks variable
			beforeStart = nil
		})

		c.Specify("runs beforeStart hooks", func() {
			hooks := []string{}

			DuringDrain(func() {
				hooks = append(hooks, "1")
			})
			DuringDrain(func() {
				hooks = append(hooks, "2")
			})
			DuringDrain(func() {
				hooks = append(hooks, "3")
			})

			Start()

			c.Expect(reflect.DeepEqual(hooks, []string{}), IsTrue)

			Quit()

			c.Expect(reflect.DeepEqual(hooks, []string{"1", "2", "3"}), IsTrue)

			// Clear out global hooks variable
			duringDrain = nil
		})
	})
}
예제 #18
0
func MsgSpec(c gospec.Context) {
	c.Specify("NewMsg", func() {
		c.Specify("unmarshals json", func() {
			msg, _ := NewMsg("{\"hello\":\"world\",\"foo\":3}")
			hello, _ := msg.Get("hello").String()
			foo, _ := msg.Get("foo").Int()

			c.Expect(hello, Equals, "world")
			c.Expect(foo, Equals, 3)
		})

		c.Specify("returns an error if invalid json", func() {
			msg, err := NewMsg("{\"hello:\"world\",\"foo\":3}")

			c.Expect(msg, IsNil)
			c.Expect(err, Not(IsNil))
		})
	})

	c.Specify("Args", func() {
		c.Specify("returns args key", func() {
			msg, _ := NewMsg("{\"hello\":\"world\",\"args\":[\"foo\",\"bar\"]}")
			c.Expect(msg.Args().ToJson(), Equals, "[\"foo\",\"bar\"]")
		})

		c.Specify("returns empty array if args key doesn't exist", func() {
			msg, _ := NewMsg("{\"hello\":\"world\"}")
			c.Expect(msg.Args().ToJson(), Equals, "[]")
		})
	})
}
예제 #19
0
func ManagerSpec(c gospec.Context) {
	processed := make(chan *Args)

	testJob := (func(message *Msg) {
		processed <- message.Args()
	})

	was := Config.Namespace
	Config.Namespace = "prod:"

	c.Specify("newManager", func() {
		c.Specify("sets queue with namespace", func() {
			manager := newManager("myqueue", testJob, 10)
			c.Expect(manager.queue, Equals, "prod:queue:myqueue")
		})

		c.Specify("sets job function", func() {
			manager := newManager("myqueue", testJob, 10)
			c.Expect(fmt.Sprint(manager.job), Equals, fmt.Sprint(testJob))
		})

		c.Specify("sets worker concurrency", func() {
			manager := newManager("myqueue", testJob, 10)
			c.Expect(manager.concurrency, Equals, 10)
		})

		c.Specify("no per-manager middleware means 'use global Middleware object'", func() {
			manager := newManager("myqueue", testJob, 10)
			c.Expect(manager.mids, Equals, Middleware)
		})

		c.Specify("per-manager middlewares create separate middleware chains", func() {
			mid1 := customMid{[]string{}, "0"}
			manager := newManager("myqueue", testJob, 10, &mid1)
			c.Expect(manager.mids, Not(Equals), Middleware)
			c.Expect(len(manager.mids.actions), Equals, len(Middleware.actions)+1)
		})

	})

	c.Specify("manage", func() {
		conn := Config.Pool.Get()
		defer conn.Close()

		message, _ := NewMsg("{\"foo\":\"bar\",\"args\":[\"foo\",\"bar\"]}")
		message2, _ := NewMsg("{\"foo\":\"bar2\",\"args\":[\"foo\",\"bar2\"]}")

		c.Specify("coordinates processing of queue messages", func() {
			manager := newManager("manager1", testJob, 10)

			conn.Do("lpush", "prod:queue:manager1", message.ToJson())
			conn.Do("lpush", "prod:queue:manager1", message2.ToJson())

			manager.start()

			c.Expect(<-processed, Equals, message.Args())
			c.Expect(<-processed, Equals, message2.Args())

			manager.quit()

			len, _ := redis.Int(conn.Do("llen", "prod:queue:manager1"))
			c.Expect(len, Equals, 0)
		})

		c.Specify("per-manager middlwares are called separately, global middleware is called in each manager", func() {
			mid1 := customMid{[]string{}, "1"}
			mid2 := customMid{[]string{}, "2"}
			mid3 := customMid{[]string{}, "3"}

			oldMiddleware := Middleware
			Middleware = NewMiddleware()
			Middleware.Append(&mid1)

			manager1 := newManager("manager1", testJob, 10)
			manager2 := newManager("manager2", testJob, 10, &mid2)
			manager3 := newManager("manager3", testJob, 10, &mid3)

			conn.Do("lpush", "prod:queue:manager1", message.ToJson())
			conn.Do("lpush", "prod:queue:manager2", message.ToJson())
			conn.Do("lpush", "prod:queue:manager3", message.ToJson())

			manager1.start()
			manager2.start()
			manager3.start()

			<-processed
			<-processed
			<-processed

			Middleware = oldMiddleware

			c.Expect(
				arrayCompare(mid1.Trace, []string{"11", "12", "11", "12", "11", "12"}),
				IsTrue,
			)
			c.Expect(
				arrayCompare(mid2.Trace, []string{"21", "22"}),
				IsTrue,
			)
			c.Expect(
				arrayCompare(mid3.Trace, []string{"31", "32"}),
				IsTrue,
			)

			manager1.quit()
			manager2.quit()
			manager3.quit()
		})

		c.Specify("prepare stops fetching new messages from queue", func() {
			manager := newManager("manager2", testJob, 10)
			manager.start()

			manager.prepare()

			conn.Do("lpush", "prod:queue:manager2", message)
			conn.Do("lpush", "prod:queue:manager2", message2)

			manager.quit()

			len, _ := redis.Int(conn.Do("llen", "prod:queue:manager2"))
			c.Expect(len, Equals, 2)
		})
	})

	Config.Namespace = was
}
예제 #20
0
func EnqueueSpec(c gospec.Context) {
	was := Config.Namespace
	Config.Namespace = "prod:"

	c.Specify("Enqueue", func() {
		conn := Config.Pool.Get()
		defer conn.Close()

		c.Specify("makes the queue available", func() {
			Enqueue("enqueue1", "Add", []int{1, 2})

			found, _ := redis.Bool(conn.Do("sismember", "prod:queues", "enqueue1"))
			c.Expect(found, IsTrue)
		})

		c.Specify("adds a job to the queue", func() {
			nb, _ := redis.Int(conn.Do("llen", "prod:queue:enqueue2"))
			c.Expect(nb, Equals, 0)

			Enqueue("enqueue2", "Add", []int{1, 2})

			nb, _ = redis.Int(conn.Do("llen", "prod:queue:enqueue2"))
			c.Expect(nb, Equals, 1)
		})

		c.Specify("saves the arguments", func() {
			Enqueue("enqueue3", "Compare", []string{"foo", "bar"})

			bytes, _ := redis.Bytes(conn.Do("lpop", "prod:queue:enqueue3"))
			var result map[string]interface{}
			json.Unmarshal(bytes, &result)
			c.Expect(result["class"], Equals, "Compare")

			args := result["args"].([]interface{})
			c.Expect(len(args), Equals, 2)
			c.Expect(args[0], Equals, "foo")
			c.Expect(args[1], Equals, "bar")
		})

		c.Specify("has a jid", func() {
			Enqueue("enqueue4", "Compare", []string{"foo", "bar"})

			bytes, _ := redis.Bytes(conn.Do("lpop", "prod:queue:enqueue4"))
			var result map[string]interface{}
			json.Unmarshal(bytes, &result)
			c.Expect(result["class"], Equals, "Compare")

			jid := result["jid"].(string)
			c.Expect(len(jid), Equals, 24)
		})

		c.Specify("has enqueued_at that is close to now", func() {
			Enqueue("enqueue5", "Compare", []string{"foo", "bar"})

			bytes, _ := redis.Bytes(conn.Do("lpop", "prod:queue:enqueue5"))
			var result map[string]interface{}
			json.Unmarshal(bytes, &result)
			c.Expect(result["class"], Equals, "Compare")

			ea := result["enqueued_at"].(float64)
			c.Expect(ea, Not(Equals), 0)
			c.Expect(ea, IsWithin(0.1), float64(time.Now().UnixNano())/1000000000)
		})

		c.Specify("has retry and retry_count when set", func() {
			EnqueueWithOptions("enqueue6", "Compare", []string{"foo", "bar"}, EnqueueOptions{RetryCount: 13, Retry: true})

			bytes, _ := redis.Bytes(conn.Do("lpop", "prod:queue:enqueue6"))
			var result map[string]interface{}
			json.Unmarshal(bytes, &result)
			c.Expect(result["class"], Equals, "Compare")

			retry := result["retry"].(bool)
			c.Expect(retry, Equals, true)

			retryCount := int(result["retry_count"].(float64))
			c.Expect(retryCount, Equals, 13)
		})
	})

	Config.Namespace = was
}