summaryrefslogtreecommitdiffhomepage
path: root/caddyhttp/fastcgi/dialer.go
blob: 135908e3f5ea0e7fad9a22bb535a90dadde1b74e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package fastcgi

import (
	"errors"
	"sync"
	"sync/atomic"
	"time"
)

type dialer interface {
	Dial() (Client, error)
	Close(Client) error
}

// basicDialer is a basic dialer that wraps default fcgi functions.
type basicDialer struct {
	network string
	address string
	timeout time.Duration
}

func (b basicDialer) Dial() (Client, error) {
	return DialTimeout(b.network, b.address, b.timeout)
}

func (b basicDialer) Close(c Client) error { return c.Close() }

// persistentDialer keeps a pool of fcgi connections.
// connections are not closed after use, rather added back to the pool for reuse.
type persistentDialer struct {
	size    int
	network string
	address string
	timeout time.Duration
	pool    []Client
	sync.Mutex
}

func (p *persistentDialer) Dial() (Client, error) {
	p.Lock()
	// connection is available, return first one.
	if len(p.pool) > 0 {
		client := p.pool[0]
		p.pool = p.pool[1:]
		p.Unlock()

		return client, nil
	}

	p.Unlock()

	// no connection available, create new one
	return DialTimeout(p.network, p.address, p.timeout)
}

func (p *persistentDialer) Close(client Client) error {
	p.Lock()
	if len(p.pool) < p.size {
		// pool is not full yet, add connection for reuse
		p.pool = append(p.pool, client)
		p.Unlock()

		return nil
	}

	p.Unlock()

	// otherwise, close the connection.
	return client.Close()
}

type loadBalancingDialer struct {
	dialers []dialer
	current int64
}

func (m *loadBalancingDialer) Dial() (Client, error) {
	nextDialerIndex := atomic.AddInt64(&m.current, 1) % int64(len(m.dialers))
	currentDialer := m.dialers[nextDialerIndex]

	client, err := currentDialer.Dial()

	if err != nil {
		return nil, err
	}

	return &dialerAwareClient{Client: client, dialer: currentDialer}, nil
}

func (m *loadBalancingDialer) Close(c Client) error {
	// Close the client according to dialer behaviour
	if da, ok := c.(*dialerAwareClient); ok {
		return da.dialer.Close(c)
	}

	return errors.New("Cannot close client")
}

type dialerAwareClient struct {
	Client
	dialer dialer
}