1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-23 15:37:01 +00:00

Make the queue aware of local and remote couriers

The routing courier is a nice idea in theory, but at least for now, we want
the queue to be aware of when a destination is local so we can implement
differentiated logic.

This may change in the future, though, but at the moment it's not clear that
the abstractions will be worth it.

So this patch removes it, and makes the queue do the routing. There is no
difference in how the two are handled yet, those will come in subsequent
patches.
This commit is contained in:
Alberto Bertogli
2016-07-19 23:26:27 +01:00
parent 362ef6f6d0
commit 9d172a6ea0
5 changed files with 90 additions and 113 deletions

View File

@@ -10,6 +10,8 @@ import (
"time"
"blitiri.com.ar/go/chasquid/internal/courier"
"blitiri.com.ar/go/chasquid/internal/envelope"
"blitiri.com.ar/go/chasquid/internal/set"
"github.com/golang/glog"
"golang.org/x/net/trace"
@@ -60,16 +62,22 @@ type Queue struct {
// Mutex protecting q.
mu sync.RWMutex
// Courier to use to deliver mail.
courier courier.Courier
// Couriers to use to deliver mail.
localC courier.Courier
remoteC courier.Courier
// Domains we consider local.
localDomains *set.String
}
// TODO: Store the queue on disk.
// Load the queue and launch the sending loops on startup.
func New(c courier.Courier) *Queue {
func New(localC, remoteC courier.Courier, localDomains *set.String) *Queue {
return &Queue{
q: map[string]*Item{},
courier: c,
q: map[string]*Item{},
localC: localC,
remoteC: remoteC,
localDomains: localDomains,
}
}
@@ -151,7 +159,6 @@ func (item *Item) SendLoop(q *Queue) {
defer tr.Finish()
tr.LazyPrintf("from: %s", item.From)
var err error
var delay time.Duration
for time.Since(item.Created) < giveUpAfter {
// Send to all recipients that are still pending.
@@ -167,12 +174,24 @@ func (item *Item) SendLoop(q *Queue) {
defer wg.Done()
tr.LazyPrintf("%s sending", to)
err = q.courier.Deliver(item.From, to, item.Data)
var err error
// TODO: If this is all the difference we end up having
// between the two couriers, consider going back to using a
// routing courier.
if envelope.DomainIn(to, q.localDomains) {
err = q.localC.Deliver(item.From, to, item.Data)
} else {
err = q.remoteC.Deliver(item.From, to, item.Data)
}
item.mu.Lock()
item.Results[to] = err
item.mu.Unlock()
if err != nil {
// TODO: Local deliveries should not be retried, if they
// fail due to the user not existing.
// -> we need to know the users.
// Or maybe we can just not care?
tr.LazyPrintf("error: %v", err)
glog.Infof("%s -> %q fail: %v", item.ID, to, err)
} else {

View File

@@ -2,12 +2,15 @@ package queue
import (
"bytes"
"sync"
"testing"
"time"
"blitiri.com.ar/go/chasquid/internal/set"
)
// Our own courier, for testing purposes.
// Delivery is done by sending on a channel.
// Test courier. Delivery is done by sending on a channel, so users have fine
// grain control over the results.
type ChanCourier struct {
requests chan deliverRequest
results chan error
@@ -23,19 +26,42 @@ func (cc *ChanCourier) Deliver(from string, to string, data []byte) error {
cc.requests <- deliverRequest{from, to, data}
return <-cc.results
}
func newCourier() *ChanCourier {
func newChanCourier() *ChanCourier {
return &ChanCourier{
requests: make(chan deliverRequest),
results: make(chan error),
}
}
func TestBasic(t *testing.T) {
courier := newCourier()
q := New(courier)
// Courier for test purposes. Never fails, and always remembers everything.
type TestCourier struct {
wg sync.WaitGroup
requests []*deliverRequest
reqFor map[string]*deliverRequest
}
id, err := q.Put("from", []string{"to"}, []byte("data"))
func (tc *TestCourier) Deliver(from string, to string, data []byte) error {
defer tc.wg.Done()
dr := &deliverRequest{from, to, data}
tc.requests = append(tc.requests, dr)
tc.reqFor[to] = dr
return nil
}
func newTestCourier() *TestCourier {
return &TestCourier{
reqFor: map[string]*deliverRequest{},
}
}
func TestBasic(t *testing.T) {
localC := newTestCourier()
remoteC := newTestCourier()
q := New(localC, remoteC, set.NewString("loco"))
localC.wg.Add(2)
remoteC.wg.Add(1)
id, err := q.Put("from", []string{"am@loco", "x@remote", "nodomain"}, []byte("data"))
if err != nil {
t.Fatalf("Put: %v", err)
}
@@ -44,31 +70,35 @@ func TestBasic(t *testing.T) {
t.Errorf("short ID: %v", id)
}
q.mu.RLock()
item := q.q[id]
q.mu.RUnlock()
localC.wg.Wait()
remoteC.wg.Wait()
if item == nil {
t.Fatalf("item not in queue, racy test?")
cases := []struct {
courier *TestCourier
expectedTo string
}{
{localC, "nodomain"},
{localC, "am@loco"},
{remoteC, "x@remote"},
}
for _, c := range cases {
req := c.courier.reqFor[c.expectedTo]
if req == nil {
t.Errorf("missing request for %q", c.expectedTo)
continue
}
if item.From != "from" || item.To[0] != "to" ||
!bytes.Equal(item.Data, []byte("data")) {
t.Errorf("different item: %#v", item)
}
// Test that we delivered the item.
req := <-courier.requests
courier.results <- nil
if req.from != "from" || req.to != "to" ||
!bytes.Equal(req.data, []byte("data")) {
t.Errorf("different courier request: %#v", req)
if req.from != "from" || req.to != c.expectedTo ||
!bytes.Equal(req.data, []byte("data")) {
t.Errorf("wrong request for %q: %v", c.expectedTo, req)
}
}
}
func TestFullQueue(t *testing.T) {
q := New(newCourier())
localC := newChanCourier()
remoteC := newChanCourier()
q := New(localC, remoteC, set.NewString())
// Force-insert maxQueueSize items in the queue.
oneID := ""