Skip to content

Commit dd88755

Browse files
committed
Added multithreaded support
It's now possible to fire up multiple event loops in separate goroutines. All that's needed is to set the `events.NumLoops` options before calling `Serve`. There are a few breaking API changes. - The events pass an evio.Conn param that represents the unique incoming socket connection. - Prewrite and Postwrite events have been removed. - Wake and Dial functions have been removed. - The Transform utility has been removed. The older version has been tagged as `v0.1.0` for vendoring purposes.
1 parent 751f591 commit dd88755

21 files changed

+1529
-2641
lines changed

README.md

Lines changed: 22 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,20 @@ This project is not intended to be a general purpose replacement for the standar
1717

1818
You would not want to use this framework if you need to handle long-running requests (milliseconds or more). For example, a web api that needs to connect to a mongo database, authenticate, and respond; just use the Go net/http package instead.
1919

20-
There are many popular event loop based applications in the wild such as Nginx, Haproxy, Redis, and Memcached. All of these are single-threaded and very fast and written in C.
21-
22-
The reason I wrote this framework is so I can build certain network services that perform like the C apps above, but I also want to continue to work in Go.
23-
20+
There are many popular event loop based applications in the wild such as Nginx, Haproxy, Redis, and Memcached. All of these are very fast and written in C.
2421

22+
The reason I wrote this framework is so that I can build certain networking services that perform like the C apps above, but I also want to continue to work in Go.
2523

2624
## Features
2725

28-
- [Fast](#performance) single-threaded event loop
26+
- [Fast](#performance) single-threaded or [multithreaded](#multithreaded) event loop
27+
- Built-in [load balancing](#load-balancing) options
2928
- Simple API
3029
- Low memory usage
3130
- Supports tcp, [udp](#udp), and unix sockets
3231
- Allows [multiple network binding](#multiple-addresses) on the same event loop
3332
- Flexible [ticker](#ticker) event
3433
- Fallback for non-epoll/kqueue operating systems by simulating events with the [net](https://golang.org/pkg/net/) package
35-
- Ability to [wake up](#wake-up) connections from long running background operations
36-
- [Dial](#dial-out) an outbound connection and process/proxy on the event loop
3734
- [SO_REUSEPORT](#so_reuseport) socket option
3835

3936
## Getting Started
@@ -61,7 +58,7 @@ import "github.com/tidwall/evio"
6158

6259
func main() {
6360
var events evio.Events
64-
events.Data = func(id int, in []byte) (out []byte, action evio.Action) {
61+
events.Data = func(c evio.Conn, in []byte) (out []byte, action evio.Action) {
6562
out = in
6663
return
6764
}
@@ -89,8 +86,6 @@ The event type has a bunch of handy events:
8986
- `Closed` fires when a connection has closed.
9087
- `Detach` fires when a connection has been detached using the `Detach` return action.
9188
- `Data` fires when the server receives new data from a connection.
92-
- `Prewrite` fires prior to all write attempts from the server.
93-
- `Postwrite` fires immediately after every write attempt.
9489
- `Tick` fires immediately after the server starts and will fire again after a specified interval.
9590

9691
### Multiple addresses
@@ -114,127 +109,30 @@ events.Tick = func() (delay time.Duration, action Action){
114109
}
115110
```
116111

117-
### Wake up
118-
119-
A connection can be woken up using the `Wake` function that is made available through the `Serving` event. This is useful for when you need to offload an operation to a background goroutine and then later notify the event loop that it's time to send some data.
120-
121-
Example echo server that when encountering the line "exec" it waits 5 seconds before responding.
122-
123-
```go
124-
var srv evio.Server
125-
var mu sync.Mutex
126-
var execs = make(map[int]int)
127-
128-
events.Serving = func(srvin evio.Server) (action evio.Action) {
129-
srv = srvin // hang on to the server control, which has the Wake function
130-
return
131-
}
132-
events.Data = func(id int, in []byte) (out []byte, action evio.Action) {
133-
if in == nil {
134-
// look for `in` param equal to `nil` following a wake call.
135-
mu.Lock()
136-
for execs[id] > 0 {
137-
out = append(out, "exec\r\n"...)
138-
execs[id]--
139-
}
140-
mu.Unlock()
141-
} else if string(in) == "exec\r\n" {
142-
go func(){
143-
// do some long running operation
144-
time.Sleep(time.Second*5)
145-
mu.Lock()
146-
execs[id]++
147-
mu.Unlock()
148-
srv.Wake(id)
149-
}()
150-
} else {
151-
out = in
152-
}
153-
return
154-
}
155-
```
156-
157-
### Dial out
158-
159-
An outbound connection can be created by using the `Dial` function that is made available through the `Serving` event. Dialing a new connection will return a new connection ID and attach that connection to the event loop in the same manner as incoming connections. This operation is completely non-blocking including any DNS resolution.
160-
161-
All new outbound connection attempts will immediately fire an `Opened` event and end with a `Closed` event. A failed connection will send the connection error through the `Closed` event.
162-
163-
```go
164-
var srv evio.Server
165-
var mu sync.Mutex
166-
var execs = make(map[int]int)
167-
168-
events.Serving = func(srvin evio.Server) (action evio.Action) {
169-
srv = srvin // hang on to the server control, which has the Dial function
170-
return
171-
}
172-
events.Data = func(id int, in []byte) (out []byte, action evio.Action) {
173-
if string(in) == "dial\r\n" {
174-
id := srv.Dial("tcp://google.com:80")
175-
// We now established an outbound connection to google.
176-
// Treat it like you would incoming connection.
177-
} else {
178-
out = in
179-
}
180-
return
181-
}
182-
```
183-
184-
### Data translations
185-
186-
The `Translate` function wraps events and provides a `ReadWriter` that can be used to translate data off the wire from one format to another. This can be useful for transparently adding compression or encryption.
187-
188-
For example, let's say we need TLS support:
189-
190-
```go
191-
var events Events
192-
193-
// ... fill the events with happy functions
194-
195-
cer, err := tls.LoadX509KeyPair("certs/ssl-cert-snakeoil.pem", "certs/ssl-cert-snakeoil.key")
196-
if err != nil {
197-
log.Fatal(err)
198-
}
199-
config := &tls.Config{Certificates: []tls.Certificate{cer}}
200-
201-
// wrap the events with a TLS translator
202-
203-
events = evio.Translate(events, nil,
204-
func(id int, rw io.ReadWriter) io.ReadWriter {
205-
return tls.Server(evio.NopConn(rw), config)
206-
},
207-
)
208-
209-
log.Fatal(evio.Serve(events, "tcp://0.0.0.0:443"))
210-
```
112+
## UDP
211113

212-
Here we wrapped the event with a TLS translator. The `evio.NopConn` function is used to converts the `ReadWriter` a `net.Conn` so the `tls.Server()` call will work.
114+
The `Serve` function can bind to UDP addresses.
213115

214-
There's a working TLS example at [examples/http-server/main.go](examples/http-server/main.go) that binds to port 8080 and 4443 using an developer SSL certificate. The 8080 connections will be insecure and the 4443 will be secure.
116+
- All incoming and outgoing packets are not buffered and sent individually.
117+
- The `Opened` and `Closed` events are not availble for UDP sockets, only the `Data` event.
215118

216-
```sh
217-
$ cd examples/http-server
218-
$ go run main.go --tlscert example.pem
219-
2017/11/02 06:24:33 http server started on port 8080
220-
2017/11/02 06:24:33 https server started on port 4443
221-
```
119+
## Multithreaded
222120

223-
```sh
224-
$ curl http://localhost:8080
225-
Hello World!
226-
$ curl -k https://localhost:4443
227-
Hello World!
228-
```
121+
The `events.NumLoops` options sets the number of loops to use for the server.
122+
Setting this to a value greater than 1 will effectively make the server multithreaded for multi-core machines.
123+
Which means you must take care with synchonizing memory between all event callbacks.
124+
Setting to 0 or 1 will run the server single-threaded.
125+
Setting to -1 will automatically assign this value equal to `runtime.NumProcs()`.
229126

230-
## UDP
127+
## Load balancing
231128

232-
The `Serve` function can bind to UDP addresses.
129+
The `events.LoadBalance` options sets the load balancing method.
130+
Load balancing is always a best effort to attempt to distribute the incoming connections between multiple loops.
131+
This option is only available when `events.NumLoops` is set.
233132

234-
- The `Opened` event will fire when a UDP packet is received from a new remote address.
235-
- The `Closed` event will fire when the server is shutdown or the `Close` action is explicitly returned from an event.
236-
- The `Wake` and `Dial` operations are not available to UDP connections.
237-
- All incoming and outgoing packets are not buffered and sent individually.
133+
- `Random` requests that connections are randomly distributed.
134+
- `RoundRobin` requests that connections are distributed to a loop in a round-robin fashion.
135+
- `LeastConnections` assigns the next accepted connection to the loop with the least number of active connections.
238136

239137
## SO_REUSEPORT
240138

0 commit comments

Comments
 (0)