mirror of https://github.com/tidwall/evio.git
first commit
This commit is contained in:
commit
f36b6b2ca7
|
@ -0,0 +1,20 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017 Joshua J Baker
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,175 @@
|
||||||
|
# `✨ shiny ✨`
|
||||||
|
|
||||||
|
Shiny is an alternative networking framework for Go that uses I/O multiplexing.
|
||||||
|
It makes direct [epoll](https://en.wikipedia.org/wiki/Epoll) and [kqueue](https://en.wikipedia.org/wiki/Kqueue) syscalls rather than the standard Go [net](https://golang.org/pkg/net/) package.
|
||||||
|
|
||||||
|
This is similar to the way that [libuv](https://github.com/libuv/libuv), [libevent](https://github.com/libevent/libevent), [haproxy](http://www.haproxy.org/), [nginx](http://nginx.org/), [redis](http://redis.io/), and other high performance network servers work.
|
||||||
|
|
||||||
|
The reason for this project is that I want to upgrade the networking for [Tile38](http://github.com/tidwall/tile38) so that it will perform on par with Redis, but without having to interop with Cgo. Early benchmarks are exceeding my expectations.
|
||||||
|
|
||||||
|
**This project is a (sweet) work in progress. The API will likely change between now and Tile38 v2.0 release.**
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Simple API. Only one entrypoint and four event functions
|
||||||
|
- Low memory usage
|
||||||
|
- Very fast single-threaded support
|
||||||
|
- Support for non-epoll/kqueue operating systems by simulating events with the net package.
|
||||||
|
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Installing
|
||||||
|
|
||||||
|
To start using Shiny, install Go and run `go get`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ go get -u github.com/tidwall/shiny
|
||||||
|
```
|
||||||
|
|
||||||
|
This will retrieve the library.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
There's only the one function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Serve(net, addr string,
|
||||||
|
handle func(id int, data []byte, ctx interface{}) (send []byte, keepopen bool),
|
||||||
|
accept func(id int, addr string, wake func(), ctx interface{}) (send []byte, keepopen bool),
|
||||||
|
closed func(id int, err error, ctx interface{}),
|
||||||
|
ticker func(ctx interface{}) (keepserving bool),
|
||||||
|
context interface{}) error
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Please check out the [examples](examples) subdirectory for a simplified [redis](examples/redis-server.go) clone and an [echo](examples/echo-server.go) server.
|
||||||
|
|
||||||
|
Here's a basic echo server:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/tidwall/shiny"
|
||||||
|
)
|
||||||
|
|
||||||
|
var shutdown bool
|
||||||
|
var started bool
|
||||||
|
var port int
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.IntVar(&port, "port", 9999, "server port")
|
||||||
|
flag.Parse()
|
||||||
|
log.Fatal(shiny.Serve("tcp", fmt.Sprintf(":%d", port),
|
||||||
|
handle, accept, closed, ticker, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle - the incoming client socket data.
|
||||||
|
func handle(id int, data []byte, ctx interface{}) (send []byte, keepopen bool) {
|
||||||
|
if shutdown {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
keepopen = true
|
||||||
|
if string(data) == "shutdown\r\n" {
|
||||||
|
shutdown = true
|
||||||
|
} else if string(data) == "quit\r\n" {
|
||||||
|
keepopen = false
|
||||||
|
}
|
||||||
|
return data, keepopen
|
||||||
|
}
|
||||||
|
|
||||||
|
// accept - a new client socket has opened.
|
||||||
|
// 'wake' is a function that when called will fire a 'handle' event
|
||||||
|
// for the specified ID, and is goroutine-safe.
|
||||||
|
func accept(id int, addr string, wake func(), ctx interface{}) (send []byte, keepopen bool) {
|
||||||
|
if shutdown {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
// this is a good place to create a user-defined socket context.
|
||||||
|
return []byte(
|
||||||
|
"Welcome to the echo server!\n" +
|
||||||
|
"Enter 'quit' to close your connection or " +
|
||||||
|
"'shutdown' to close the server.\n"), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// closed - a client socket has closed
|
||||||
|
func closed(id int, err error, ctx interface{}) {
|
||||||
|
// teardown the socket context here
|
||||||
|
}
|
||||||
|
|
||||||
|
// ticker - a ticker that fires between 1 and 1/20 of a second
|
||||||
|
// depending on the traffic.
|
||||||
|
func ticker(ctx interface{}) (keepserving bool) {
|
||||||
|
if shutdown {
|
||||||
|
// do server teardown here
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !started {
|
||||||
|
fmt.Printf("echo server started on port %d\n", port)
|
||||||
|
started = true
|
||||||
|
}
|
||||||
|
// perform various non-socket-io related operation here
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go run examples/echo-server.go
|
||||||
|
```
|
||||||
|
|
||||||
|
Connect to the server:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ telnet localhost 9999
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
The benchmarks below use pipelining which allows for combining multiple Redis commands into a single packet.
|
||||||
|
|
||||||
|
**Redis**
|
||||||
|
|
||||||
|
```
|
||||||
|
$ redis-server --port 6379 --appendonly no
|
||||||
|
```
|
||||||
|
```
|
||||||
|
redis-benchmark -p 6379 -t ping,set,get -q -P 128
|
||||||
|
PING_INLINE: 961538.44 requests per second
|
||||||
|
PING_BULK: 1960784.38 requests per second
|
||||||
|
SET: 943396.25 requests per second
|
||||||
|
GET: 1369863.00 requests per second
|
||||||
|
```
|
||||||
|
|
||||||
|
**Shiny**
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go run examples/redis-server.go --port 6380 --appendonly no
|
||||||
|
```
|
||||||
|
```
|
||||||
|
redis-benchmark -p 6380 -t ping,set,get -q -P 128
|
||||||
|
PING_INLINE: 3846153.75 requests per second
|
||||||
|
PING_BULK: 4166666.75 requests per second
|
||||||
|
SET: 3703703.50 requests per second
|
||||||
|
GET: 3846153.75 requests per second
|
||||||
|
```
|
||||||
|
|
||||||
|
*Running on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.7*
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
Josh Baker [@tidwall](http://twitter.com/tidwall)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Shiny source code is available under the MIT [License](/LICENSE).
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# `✨ shiny ✨ examples`
|
||||||
|
|
||||||
|
## redis-server
|
||||||
|
|
||||||
|
```
|
||||||
|
go run examples/redis-server.go [--port int] [--appendonly yes/no]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `GET`, `SET`, `DEL`, `QUIT`, `PING`, `SHUTDOWN` commands.
|
||||||
|
- `--appendonly yes` option for disk persistence.
|
||||||
|
- Compatible with the [redis-cli](https://redis.io/topics/rediscli) and all [redis clients](https://redis.io/clients).
|
||||||
|
|
||||||
|
|
||||||
|
## echo-server
|
||||||
|
|
||||||
|
```
|
||||||
|
go run examples/echo-server.go [--port int]
|
||||||
|
```
|
|
@ -0,0 +1,64 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tidwall/shiny"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var port int
|
||||||
|
flag.IntVar(&port, "port", 9999, "server port")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var shutdown bool
|
||||||
|
var started bool
|
||||||
|
fmt.Println(shiny.Serve("tcp", fmt.Sprintf(":%d", port),
|
||||||
|
// handle - the incoming client socket data.
|
||||||
|
func(id int, data []byte, ctx interface{}) (send []byte, keepopen bool) {
|
||||||
|
if shutdown {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
keepopen = true
|
||||||
|
if string(data) == "shutdown\r\n" {
|
||||||
|
shutdown = true
|
||||||
|
} else if string(data) == "quit\r\n" {
|
||||||
|
keepopen = false
|
||||||
|
}
|
||||||
|
return data, keepopen
|
||||||
|
},
|
||||||
|
// accept - a new client socket has opened.
|
||||||
|
// 'wake' is a function that when called will fire a 'handle' event
|
||||||
|
// for the specified ID, and is goroutine-safe.
|
||||||
|
func(id int, addr string, wake func(), ctx interface{}) (send []byte, keepopen bool) {
|
||||||
|
if shutdown {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
// this is a good place to create a user-defined socket context.
|
||||||
|
return []byte(
|
||||||
|
"Welcome to the echo server!\n" +
|
||||||
|
"Enter 'quit' to close your connection or " +
|
||||||
|
"'shutdown' to close the server.\n"), true
|
||||||
|
},
|
||||||
|
// closed - a client socket has closed
|
||||||
|
func(id int, err error, ctx interface{}) {
|
||||||
|
// teardown the socket context here
|
||||||
|
},
|
||||||
|
// ticker - a ticker that fires between 1 and 1/20 of a second
|
||||||
|
// depending on the traffic.
|
||||||
|
func(ctx interface{}) (keepserveropen bool) {
|
||||||
|
if shutdown {
|
||||||
|
// do server teardown here
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !started {
|
||||||
|
fmt.Printf("echo server started on port %d\n", port)
|
||||||
|
started = true
|
||||||
|
}
|
||||||
|
// perform various non-socket-io related operation here
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
// an optional user-defined context
|
||||||
|
nil))
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tidwall/redcon"
|
||||||
|
"github.com/tidwall/shiny"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var port int
|
||||||
|
var appendonly string
|
||||||
|
flag.IntVar(&port, "port", 6380, "server port")
|
||||||
|
flag.StringVar(&appendonly, "appendonly", "no", "use appendonly file (yes or no)")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var resp []byte
|
||||||
|
var aofw []byte
|
||||||
|
var args [][]byte
|
||||||
|
var shutdown bool
|
||||||
|
var started bool
|
||||||
|
var bufs = make(map[int][]byte)
|
||||||
|
var keys = make(map[string]string)
|
||||||
|
|
||||||
|
var f *os.File
|
||||||
|
if appendonly == "yes" {
|
||||||
|
var err error
|
||||||
|
f, err = os.OpenFile("appendonly.aof", os.O_CREATE|os.O_RDWR, 0600)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
rd := redcon.NewReader(f)
|
||||||
|
for {
|
||||||
|
cmd, err := rd.ReadCommand()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
switch strings.ToUpper(string(cmd.Args[0])) {
|
||||||
|
default:
|
||||||
|
log.Fatal("bad aof")
|
||||||
|
case "SET":
|
||||||
|
keys[string(cmd.Args[1])] = string(cmd.Args[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Fatal(shiny.Serve("tcp", fmt.Sprintf(":%d", port),
|
||||||
|
func(id int, data []byte, ctx interface{}) (send []byte, keepopen bool) {
|
||||||
|
if shutdown {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
buf := bufs[id]
|
||||||
|
if len(buf) > 0 {
|
||||||
|
data = append(buf, data...)
|
||||||
|
}
|
||||||
|
keepopen = true
|
||||||
|
resp = resp[:0]
|
||||||
|
aofw = aofw[:0]
|
||||||
|
var complete bool
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
complete, args, _, data, err = redcon.ReadNextCommand(data, args[:0])
|
||||||
|
if err != nil {
|
||||||
|
keepopen = false
|
||||||
|
resp = redcon.AppendError(resp, err.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !complete {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch strings.ToUpper(string(args[0])) {
|
||||||
|
default:
|
||||||
|
resp = redcon.AppendError(resp, fmt.Sprintf("ERR unknown command '%s'", args[0]))
|
||||||
|
case "PING":
|
||||||
|
if len(args) > 2 {
|
||||||
|
resp = redcon.AppendError(resp, fmt.Sprintf("ERR wrong number of arguments for '%s' command", args[0]))
|
||||||
|
continue
|
||||||
|
} else if len(args) == 1 {
|
||||||
|
resp = redcon.AppendString(resp, "PONG")
|
||||||
|
} else {
|
||||||
|
resp = redcon.AppendBulk(resp, args[1])
|
||||||
|
}
|
||||||
|
case "QUIT":
|
||||||
|
keepopen = false
|
||||||
|
resp = redcon.AppendOK(resp)
|
||||||
|
case "SHUTDOWN":
|
||||||
|
shutdown = true
|
||||||
|
keepopen = false
|
||||||
|
resp = redcon.AppendOK(resp)
|
||||||
|
case "SET":
|
||||||
|
if len(args) != 3 {
|
||||||
|
resp = redcon.AppendError(resp, fmt.Sprintf("ERR wrong number of arguments for '%s' command", args[0]))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keys[string(args[1])] = string(args[2])
|
||||||
|
resp = redcon.AppendOK(resp)
|
||||||
|
if appendonly == "yes" {
|
||||||
|
// create the append only entry
|
||||||
|
aofw = redcon.AppendArray(aofw, 3)
|
||||||
|
aofw = redcon.AppendBulkString(aofw, "SET")
|
||||||
|
aofw = redcon.AppendBulk(aofw, args[1])
|
||||||
|
aofw = redcon.AppendBulk(aofw, args[2])
|
||||||
|
}
|
||||||
|
case "GET":
|
||||||
|
if len(args) != 2 {
|
||||||
|
resp = redcon.AppendError(resp, fmt.Sprintf("ERR wrong number of arguments for '%s' command", args[0]))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val, ok := keys[string(args[1])]
|
||||||
|
if !ok {
|
||||||
|
resp = redcon.AppendNull(resp)
|
||||||
|
} else {
|
||||||
|
resp = redcon.AppendBulkString(resp, val)
|
||||||
|
}
|
||||||
|
case "DEL":
|
||||||
|
if len(args) < 2 {
|
||||||
|
resp = redcon.AppendError(resp, fmt.Sprintf("ERR wrong number of arguments for '%s' command", args[0]))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var n int64
|
||||||
|
for i := 1; i < len(args); i++ {
|
||||||
|
if _, ok := keys[string(args[i])]; ok {
|
||||||
|
delete(keys, string(args[i]))
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp = redcon.AppendInt(resp, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(data) > 0 {
|
||||||
|
bufs[id] = append(buf[:0], data...)
|
||||||
|
} else if len(buf) > 0 {
|
||||||
|
bufs[id] = buf[:0]
|
||||||
|
}
|
||||||
|
if len(aofw) > 0 {
|
||||||
|
if _, err := f.Write(aofw); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp, keepopen
|
||||||
|
},
|
||||||
|
// accept - a new client socket has opened
|
||||||
|
func(id int, addr string, wake func(), ctx interface{}) (send []byte, keepopen bool) {
|
||||||
|
if shutdown {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
// create a new socket context here
|
||||||
|
return nil, true
|
||||||
|
},
|
||||||
|
// closed - a client socket has closed
|
||||||
|
func(id int, err error, ctx interface{}) {
|
||||||
|
// teardown the socket context here
|
||||||
|
delete(bufs, id)
|
||||||
|
},
|
||||||
|
// ticker - a ticker that fires between 1 and 1/20 of a second
|
||||||
|
// depending on the traffic.
|
||||||
|
func(ctx interface{}) (keepserveropen bool) {
|
||||||
|
if shutdown {
|
||||||
|
// do server teardown here
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !started {
|
||||||
|
fmt.Printf("redis(ish) server started on port %d\n", port)
|
||||||
|
started = true
|
||||||
|
}
|
||||||
|
// perform various non-socket-io related operation here
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
// an optional user-defined context
|
||||||
|
nil))
|
||||||
|
}
|
|
@ -0,0 +1,202 @@
|
||||||
|
package shiny
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Serve
|
||||||
|
func Serve(net, addr string,
|
||||||
|
handle func(id int, data []byte, ctx interface{}) (send []byte, keepopen bool),
|
||||||
|
accept func(id int, addr string, wake func(), ctx interface{}) (send []byte, keepopen bool),
|
||||||
|
closed func(id int, err error, ctx interface{}),
|
||||||
|
ticker func(ctx interface{}) (keepopen bool),
|
||||||
|
context interface{}) error {
|
||||||
|
if strings.HasSuffix(net, "-compat") {
|
||||||
|
net = net[:len(net)-len("-compat")]
|
||||||
|
} else {
|
||||||
|
switch net {
|
||||||
|
case "tcp", "tcp4", "tcp6":
|
||||||
|
return eventServe(net, addr, handle, accept, closed, ticker, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return compatServe(net, addr, handle, accept, closed, ticker, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compatServe(net_, addr string,
|
||||||
|
handle func(id int, data []byte, ctx interface{}) (send []byte, keepopen bool),
|
||||||
|
accept func(id int, addr string, wake func(), ctx interface{}) (send []byte, keepopen bool),
|
||||||
|
closed func(id int, err error, ctx interface{}),
|
||||||
|
ticker func(ctx interface{}) (keepopen bool),
|
||||||
|
ctx interface{}) error {
|
||||||
|
if handle == nil {
|
||||||
|
handle = func(id int, data []byte, ctx interface{}) (send []byte, keepopen bool) {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if accept == nil {
|
||||||
|
accept = func(id int, addr string, wake func(), ctx interface{}) (send []byte, keepopen bool) {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if closed == nil {
|
||||||
|
closed = func(id int, err error, ctx interface{}) {}
|
||||||
|
}
|
||||||
|
if ticker == nil {
|
||||||
|
ticker = func(ctx interface{}) (keepopen bool) { return true }
|
||||||
|
}
|
||||||
|
|
||||||
|
ln, err := net.Listen(net_, addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ln.Close()
|
||||||
|
if !ticker(ctx) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var mu sync.Mutex
|
||||||
|
var conns = make(map[net.Conn]bool)
|
||||||
|
defer func() {
|
||||||
|
mu.Lock()
|
||||||
|
for c := range conns {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
}()
|
||||||
|
var id int
|
||||||
|
var done bool
|
||||||
|
var shutdown bool
|
||||||
|
donech := make(chan bool)
|
||||||
|
var lastwrite time.Time
|
||||||
|
lasttick := time.Now()
|
||||||
|
go func() {
|
||||||
|
t := time.NewTicker(time.Second / 20)
|
||||||
|
defer t.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-donech:
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(lastwrite) < time.Second || now.Sub(lasttick) >= time.Second {
|
||||||
|
mu.Lock()
|
||||||
|
if !done && !ticker(ctx) {
|
||||||
|
shutdown = true
|
||||||
|
ln.Close()
|
||||||
|
}
|
||||||
|
lasttick = now
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer func() {
|
||||||
|
mu.Lock()
|
||||||
|
done = true
|
||||||
|
mu.Unlock()
|
||||||
|
donech <- true
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
c, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
mu.Lock()
|
||||||
|
if shutdown {
|
||||||
|
mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
id++
|
||||||
|
func() {
|
||||||
|
wake := func(c net.Conn, id int) func() {
|
||||||
|
return func() {
|
||||||
|
go func() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
send, keepopen := handle(id, nil, ctx)
|
||||||
|
if len(send) > 0 {
|
||||||
|
lastwrite = time.Now()
|
||||||
|
if _, err := c.Write(send); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !keepopen {
|
||||||
|
c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}(c, id)
|
||||||
|
if !func() bool {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
send, keepopen := accept(id, c.RemoteAddr().String(), wake, ctx)
|
||||||
|
if len(send) > 0 {
|
||||||
|
lastwrite = time.Now()
|
||||||
|
if _, err := c.Write(send); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !keepopen {
|
||||||
|
c.Close()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
conns[c] = true
|
||||||
|
return true
|
||||||
|
}() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go func(id int, c net.Conn) {
|
||||||
|
var ferr error
|
||||||
|
defer func() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if ferr == io.EOF {
|
||||||
|
ferr = nil
|
||||||
|
}
|
||||||
|
if operr, ok := ferr.(*net.OpError); ok {
|
||||||
|
ferr = operr.Err
|
||||||
|
switch ferr.Error() {
|
||||||
|
case "use of closed network connection",
|
||||||
|
"read: connection reset by peer":
|
||||||
|
ferr = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(conns, c)
|
||||||
|
closed(id, ferr, ctx)
|
||||||
|
c.Close()
|
||||||
|
}()
|
||||||
|
packet := make([]byte, 4096)
|
||||||
|
for {
|
||||||
|
n, err := c.Read(packet)
|
||||||
|
if err != nil {
|
||||||
|
ferr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
send, keepopen := handle(id, packet[:n], ctx)
|
||||||
|
if len(send) > 0 {
|
||||||
|
lastwrite = time.Now()
|
||||||
|
if _, err := c.Write(send); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !keepopen {
|
||||||
|
c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}(id, c)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,285 @@
|
||||||
|
//+build linux
|
||||||
|
|
||||||
|
package shiny
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type conn struct {
|
||||||
|
fd int
|
||||||
|
id int
|
||||||
|
addr string
|
||||||
|
err error
|
||||||
|
hup bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventServe(net_, addr string,
|
||||||
|
handle func(id int, data []byte, ctx interface{}) (send []byte, keepopen bool),
|
||||||
|
accept func(id int, addr string, wake func(), ctx interface{}) (send []byte, keepopen bool),
|
||||||
|
closed func(id int, err error, ctx interface{}),
|
||||||
|
ticker func(ctx interface{}) (keepopen bool),
|
||||||
|
ctx interface{}) error {
|
||||||
|
|
||||||
|
lna, err := net.Listen(net_, addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer lna.Close()
|
||||||
|
ln := lna.(*net.TCPListener)
|
||||||
|
f, err := ln.File()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
ln.Close()
|
||||||
|
sfd := int(f.Fd())
|
||||||
|
|
||||||
|
q, err := syscall.EpollCreate1(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer syscall.Close(q)
|
||||||
|
event := syscall.EpollEvent{
|
||||||
|
Fd: int32(sfd),
|
||||||
|
Events: syscall.EPOLLIN,
|
||||||
|
}
|
||||||
|
if err := syscall.EpollCtl(q, syscall.EPOLL_CTL_ADD, sfd, &event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var conns = make(map[int]*conn)
|
||||||
|
closeAndRemove := func(c *conn) {
|
||||||
|
if c.hup {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.hup = true
|
||||||
|
syscall.Close(c.fd)
|
||||||
|
delete(conns, c.fd)
|
||||||
|
closed(c.id, c.err, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
for _, c := range conns {
|
||||||
|
closeAndRemove(c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var lastwriteorwake time.Time
|
||||||
|
write := func(nfd int, send []byte) (err error) {
|
||||||
|
res1, res2, errn := syscall.Syscall(syscall.SYS_WRITE, uintptr(nfd),
|
||||||
|
uintptr(unsafe.Pointer(&send[0])), uintptr(len(send)))
|
||||||
|
if errn != 0 {
|
||||||
|
_, _ = res1, res2
|
||||||
|
if errn == syscall.EAGAIN {
|
||||||
|
// This should not happen because we are running the
|
||||||
|
// server in blocking mode and withoug socket timeouts.
|
||||||
|
panic("EAGAIN")
|
||||||
|
}
|
||||||
|
return errn
|
||||||
|
}
|
||||||
|
lastwriteorwake = time.Now()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// add wake event
|
||||||
|
var wakemu sync.Mutex
|
||||||
|
var wakeable = true
|
||||||
|
var wakers = make(map[int]int) // FD->ID map
|
||||||
|
var wakersarr []int
|
||||||
|
var wakezero = []byte{0, 1, 2, 3, 4, 5, 6, 7}
|
||||||
|
// SYS_EVENTFD is not implemented in Go yet.
|
||||||
|
// SYS_EVENTFD = 284
|
||||||
|
// SYS_EVENTFD2 = 290
|
||||||
|
r1, _, errn := syscall.Syscall(284, 0, 0, 0)
|
||||||
|
if errn != 0 {
|
||||||
|
return errn
|
||||||
|
}
|
||||||
|
var efd = int(r1)
|
||||||
|
defer func() {
|
||||||
|
wakemu.Lock()
|
||||||
|
wakeable = false
|
||||||
|
syscall.Close(efd)
|
||||||
|
wakemu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
event = syscall.EpollEvent{
|
||||||
|
Fd: int32(efd),
|
||||||
|
Events: syscall.EPOLLIN,
|
||||||
|
}
|
||||||
|
if err := syscall.EpollCtl(q, syscall.EPOLL_CTL_ADD, efd, &event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
shandle := func(c *conn, data []byte) {
|
||||||
|
send, keepalive := handle(c.id, data, ctx)
|
||||||
|
if len(send) > 0 {
|
||||||
|
if err := write(c.fd, send); err != nil {
|
||||||
|
c.err = err
|
||||||
|
closeAndRemove(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !keepalive {
|
||||||
|
closeAndRemove(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastts time.Time
|
||||||
|
if ticker != nil {
|
||||||
|
if !ticker(ctx) {
|
||||||
|
syscall.Close(q)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
lastts = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
var id int
|
||||||
|
var packet [65535]byte
|
||||||
|
var evs [32]syscall.EpollEvent
|
||||||
|
for {
|
||||||
|
var ts int
|
||||||
|
if time.Since(lastwriteorwake) < time.Second {
|
||||||
|
ts = 50
|
||||||
|
} else {
|
||||||
|
ts = 1000
|
||||||
|
}
|
||||||
|
n, err := syscall.EpollWait(q, evs[:], ts)
|
||||||
|
if err != nil {
|
||||||
|
if err == syscall.EINTR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(lastts) >= time.Second/20 {
|
||||||
|
if !ticker(ctx) {
|
||||||
|
syscall.Close(q)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lastts = now
|
||||||
|
}
|
||||||
|
if i >= n {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if evs[i].Fd == int32(sfd) {
|
||||||
|
nfd, sa, err := syscall.Accept(sfd)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var addr string
|
||||||
|
var port int
|
||||||
|
switch sa := sa.(type) {
|
||||||
|
default:
|
||||||
|
case *syscall.SockaddrInet4:
|
||||||
|
addr = net.IP(sa.Addr[:]).String()
|
||||||
|
port = sa.Port
|
||||||
|
case *syscall.SockaddrInet6:
|
||||||
|
addr = net.IP(sa.Addr[:]).String()
|
||||||
|
port = sa.Port
|
||||||
|
}
|
||||||
|
var res []byte
|
||||||
|
if strings.Contains(addr, ":") {
|
||||||
|
res = append(append(append(res, '['), addr...), ']', ':')
|
||||||
|
} else {
|
||||||
|
res = append(append(res, addr...), ':')
|
||||||
|
}
|
||||||
|
addr = string(strconv.AppendInt(res, int64(port), 10))
|
||||||
|
|
||||||
|
id++
|
||||||
|
wake := func(nfd, id int) func() {
|
||||||
|
return func() {
|
||||||
|
// NOTE: This is the one and only entrypoint that is
|
||||||
|
// not thread-safe. Use a mutex.
|
||||||
|
wakemu.Lock()
|
||||||
|
if wakeable {
|
||||||
|
wakers[nfd] = id
|
||||||
|
syscall.Write(efd, wakezero[:])
|
||||||
|
}
|
||||||
|
wakemu.Unlock()
|
||||||
|
}
|
||||||
|
}(nfd, id)
|
||||||
|
|
||||||
|
send, keepalive := accept(id, addr, wake, ctx)
|
||||||
|
if !keepalive {
|
||||||
|
syscall.Close(nfd)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 500 second keepalive
|
||||||
|
kerr1 := syscall.SetsockoptInt(nfd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1)
|
||||||
|
kerr2 := syscall.SetsockoptInt(nfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, 500)
|
||||||
|
kerr3 := syscall.SetsockoptInt(nfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, 500)
|
||||||
|
if kerr1 != nil || kerr2 != nil || kerr3 != nil {
|
||||||
|
//fmt.Printf("%v %v %v\n", kerr1, kerr2, kerr3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add read
|
||||||
|
event := syscall.EpollEvent{
|
||||||
|
Fd: int32(nfd),
|
||||||
|
Events: syscall.EPOLLIN | syscall.EPOLLRDHUP, // | (syscall.EPOLLET & 0xffffffff),
|
||||||
|
}
|
||||||
|
if err := syscall.EpollCtl(q, syscall.EPOLL_CTL_ADD, nfd, &event); err != nil {
|
||||||
|
syscall.Close(nfd)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c := &conn{fd: nfd, addr: addr, id: id}
|
||||||
|
conns[nfd] = c
|
||||||
|
if len(send) > 0 {
|
||||||
|
if err := write(c.fd, send); err != nil {
|
||||||
|
c.err = err
|
||||||
|
closeAndRemove(c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if evs[i].Fd == int32(efd) {
|
||||||
|
// NOTE: This is a wakeup call. Use a mutex when accessing
|
||||||
|
// the `wakers` field.
|
||||||
|
wakersarr = wakersarr[:0]
|
||||||
|
wakemu.Lock()
|
||||||
|
for nfd, id := range wakers {
|
||||||
|
wakersarr = append(wakersarr, nfd, id)
|
||||||
|
}
|
||||||
|
wakers = make(map[int]int)
|
||||||
|
var data [8]byte
|
||||||
|
_, err := syscall.Read(efd, data[:])
|
||||||
|
wakemu.Unlock()
|
||||||
|
// exit the lock and read from the array
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < len(wakersarr); i += 2 {
|
||||||
|
nfd := wakersarr[i]
|
||||||
|
id := wakersarr[i+1]
|
||||||
|
c := conns[nfd]
|
||||||
|
if c != nil && c.id == id {
|
||||||
|
shandle(c, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastwriteorwake = time.Now()
|
||||||
|
} else if evs[i].Events&syscall.EPOLLRDHUP != 0 {
|
||||||
|
closeAndRemove(conns[int(evs[i].Fd)])
|
||||||
|
} else {
|
||||||
|
c := conns[int(evs[i].Fd)]
|
||||||
|
res, _, errn := syscall.Syscall(syscall.SYS_READ, uintptr(c.fd),
|
||||||
|
uintptr(unsafe.Pointer(&packet[0])), uintptr(len(packet)))
|
||||||
|
if errn != 0 || res == 0 {
|
||||||
|
if errn != 0 {
|
||||||
|
c.err = errn
|
||||||
|
}
|
||||||
|
closeAndRemove(c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
shandle(c, packet[:res])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,291 @@
|
||||||
|
//+build darwin freebsd
|
||||||
|
|
||||||
|
package shiny
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type conn struct {
|
||||||
|
fd int
|
||||||
|
id int
|
||||||
|
addr string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventServe(net_, addr string,
|
||||||
|
handle func(id int, data []byte, ctx interface{}) (send []byte, keepopen bool),
|
||||||
|
accept func(id int, addr string, wake func(), ctx interface{}) (send []byte, keepopen bool),
|
||||||
|
closed func(id int, err error, ctx interface{}),
|
||||||
|
ticker func(ctx interface{}) (keepopen bool),
|
||||||
|
ctx interface{}) error {
|
||||||
|
|
||||||
|
lna, err := net.Listen(net_, addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer lna.Close()
|
||||||
|
ln := lna.(*net.TCPListener)
|
||||||
|
f, err := ln.File()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
ln.Close()
|
||||||
|
sfd := int(f.Fd())
|
||||||
|
q, err := syscall.Kqueue()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer syscall.Close(q)
|
||||||
|
ev := []syscall.Kevent_t{{
|
||||||
|
Ident: uint64(sfd),
|
||||||
|
Flags: syscall.EV_ADD,
|
||||||
|
Filter: syscall.EVFILT_READ,
|
||||||
|
}}
|
||||||
|
if _, err := syscall.Kevent(q, ev, nil, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var conns = make(map[int]*conn)
|
||||||
|
defer func() {
|
||||||
|
for _, conn := range conns {
|
||||||
|
syscall.Close(conn.fd)
|
||||||
|
closed(conn.id, conn.err, ctx)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var lastwriteorwake time.Time
|
||||||
|
write := func(nfd int, send []byte) (err error) {
|
||||||
|
res1, res2, errn := syscall.Syscall(syscall.SYS_WRITE, uintptr(nfd),
|
||||||
|
uintptr(unsafe.Pointer(&send[0])), uintptr(len(send)))
|
||||||
|
if errn != 0 {
|
||||||
|
_, _ = res1, res2
|
||||||
|
if errn == syscall.EAGAIN {
|
||||||
|
// This should not happen because we are running the
|
||||||
|
// server in blocking mode and withoug socket timeouts.
|
||||||
|
panic("EAGAIN")
|
||||||
|
}
|
||||||
|
return errn
|
||||||
|
}
|
||||||
|
lastwriteorwake = time.Now()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// add wake event
|
||||||
|
var wakemu sync.Mutex
|
||||||
|
var wakeable = true
|
||||||
|
var wakers = make(map[int]int) // FD->ID map
|
||||||
|
var wakersarr []int
|
||||||
|
defer func() {
|
||||||
|
wakemu.Lock()
|
||||||
|
wakeable = false
|
||||||
|
wakemu.Unlock()
|
||||||
|
}()
|
||||||
|
ev = []syscall.Kevent_t{{
|
||||||
|
Ident: 0,
|
||||||
|
Flags: syscall.EV_ADD,
|
||||||
|
Filter: syscall.EVFILT_USER,
|
||||||
|
}}
|
||||||
|
if _, err := syscall.Kevent(q, ev, nil, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
shandle := func(c *conn, data []byte) {
|
||||||
|
send, keepalive := handle(c.id, data, ctx)
|
||||||
|
if len(send) > 0 {
|
||||||
|
if err := write(c.fd, send); err != nil {
|
||||||
|
c.err = err
|
||||||
|
syscall.Close(c.fd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !keepalive {
|
||||||
|
syscall.Close(c.fd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastts time.Time
|
||||||
|
if ticker != nil {
|
||||||
|
if !ticker(ctx) {
|
||||||
|
syscall.Close(q)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
lastts = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
var id int
|
||||||
|
var packet [65535]byte
|
||||||
|
var evs [32]syscall.Kevent_t
|
||||||
|
for {
|
||||||
|
var ts syscall.Timespec
|
||||||
|
if time.Since(lastwriteorwake) < time.Second {
|
||||||
|
ts = syscall.Timespec{Sec: 0, Nsec: int64(time.Second / 20)}
|
||||||
|
} else {
|
||||||
|
ts = syscall.Timespec{Sec: 1, Nsec: 0}
|
||||||
|
}
|
||||||
|
n, err := syscall.Kevent(q, nil, evs[:], &ts)
|
||||||
|
if err != nil {
|
||||||
|
if err == syscall.EINTR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(lastts) >= time.Second/20 {
|
||||||
|
if !ticker(ctx) {
|
||||||
|
syscall.Close(q)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lastts = now
|
||||||
|
}
|
||||||
|
if i >= n {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if evs[i].Flags&syscall.EV_EOF != 0 {
|
||||||
|
c := conns[int(evs[i].Ident)]
|
||||||
|
syscall.Close(int(evs[i].Ident))
|
||||||
|
delete(conns, int(evs[i].Ident))
|
||||||
|
if c != nil {
|
||||||
|
closed(c.id, c.err, ctx)
|
||||||
|
}
|
||||||
|
} else if evs[i].Ident == uint64(sfd) {
|
||||||
|
nfd, sa, err := syscall.Accept(sfd)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var addr string
|
||||||
|
var port int
|
||||||
|
switch sa := sa.(type) {
|
||||||
|
default:
|
||||||
|
case *syscall.SockaddrInet4:
|
||||||
|
addr = net.IP(sa.Addr[:]).String()
|
||||||
|
port = sa.Port
|
||||||
|
case *syscall.SockaddrInet6:
|
||||||
|
addr = net.IP(sa.Addr[:]).String()
|
||||||
|
port = sa.Port
|
||||||
|
}
|
||||||
|
var res []byte
|
||||||
|
if strings.Contains(addr, ":") {
|
||||||
|
res = append(append(append(res, '['), addr...), ']', ':')
|
||||||
|
} else {
|
||||||
|
res = append(append(res, addr...), ':')
|
||||||
|
}
|
||||||
|
addr = string(strconv.AppendInt(res, int64(port), 10))
|
||||||
|
|
||||||
|
id++
|
||||||
|
wake := func(nfd, id int) func() {
|
||||||
|
return func() {
|
||||||
|
// NOTE: This is the one and only entrypoint that is
|
||||||
|
// not thread-safe. Use a mutex.
|
||||||
|
wakemu.Lock()
|
||||||
|
ev := []syscall.Kevent_t{{
|
||||||
|
Ident: 0,
|
||||||
|
Flags: syscall.EV_ENABLE,
|
||||||
|
Filter: syscall.EVFILT_USER,
|
||||||
|
Fflags: syscall.NOTE_TRIGGER,
|
||||||
|
}}
|
||||||
|
if wakeable {
|
||||||
|
wakers[nfd] = id
|
||||||
|
syscall.Kevent(q, ev, nil, nil)
|
||||||
|
}
|
||||||
|
wakemu.Unlock()
|
||||||
|
}
|
||||||
|
}(nfd, id)
|
||||||
|
|
||||||
|
send, keepalive := accept(id, addr, wake, ctx)
|
||||||
|
if !keepalive {
|
||||||
|
syscall.Close(nfd)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 500 second keepalive
|
||||||
|
var kerr1, kerr2, kerr3 error
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
kerr1 = syscall.SetsockoptInt(nfd, syscall.SOL_SOCKET, 0x8, 1)
|
||||||
|
kerr2 = syscall.SetsockoptInt(nfd, syscall.IPPROTO_TCP, 0x101, 500)
|
||||||
|
kerr3 = syscall.SetsockoptInt(nfd, syscall.IPPROTO_TCP, 0x10, 500)
|
||||||
|
} else {
|
||||||
|
// freebsd
|
||||||
|
kerr1 = syscall.SetsockoptInt(nfd, syscall.SOL_SOCKET, 0x8, 1)
|
||||||
|
kerr2 = syscall.SetsockoptInt(nfd, syscall.IPPROTO_TCP, 0x200, 500)
|
||||||
|
kerr3 = syscall.SetsockoptInt(nfd, syscall.IPPROTO_TCP, 0x100, 500)
|
||||||
|
}
|
||||||
|
if kerr1 != nil || kerr2 != nil || kerr3 != nil {
|
||||||
|
fmt.Printf("%v %v %v\n", kerr1, kerr2, kerr3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add read
|
||||||
|
ev := []syscall.Kevent_t{{
|
||||||
|
Ident: uint64(nfd),
|
||||||
|
Flags: syscall.EV_ADD,
|
||||||
|
Filter: syscall.EVFILT_READ,
|
||||||
|
}}
|
||||||
|
if _, err := syscall.Kevent(q, ev, nil, nil); err != nil {
|
||||||
|
syscall.Close(nfd)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c := &conn{fd: nfd, addr: addr, id: id}
|
||||||
|
conns[nfd] = c
|
||||||
|
if len(send) > 0 {
|
||||||
|
if err := write(c.fd, send); err != nil {
|
||||||
|
c.err = err
|
||||||
|
syscall.Close(c.fd)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if evs[i].Ident == 0 {
|
||||||
|
// NOTE: This is a wakeup call. Use a mutex when accessing
|
||||||
|
// the `wakers` field.
|
||||||
|
wakersarr = wakersarr[:0]
|
||||||
|
wakemu.Lock()
|
||||||
|
for nfd, id := range wakers {
|
||||||
|
wakersarr = append(wakersarr, nfd, id)
|
||||||
|
}
|
||||||
|
wakers = make(map[int]int)
|
||||||
|
ev := []syscall.Kevent_t{{
|
||||||
|
Ident: 0,
|
||||||
|
Flags: syscall.EV_DISABLE,
|
||||||
|
Filter: syscall.EVFILT_USER,
|
||||||
|
Fflags: syscall.NOTE_TRIGGER,
|
||||||
|
}}
|
||||||
|
_, err := syscall.Kevent(q, ev, nil, nil)
|
||||||
|
wakemu.Unlock()
|
||||||
|
// exit the lock and read from the array
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < len(wakersarr); i += 2 {
|
||||||
|
nfd := wakersarr[i]
|
||||||
|
id := wakersarr[i+1]
|
||||||
|
c := conns[nfd]
|
||||||
|
if c != nil && c.id == id {
|
||||||
|
shandle(c, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastwriteorwake = time.Now()
|
||||||
|
} else {
|
||||||
|
c := conns[int(evs[i].Ident)]
|
||||||
|
res, _, errn := syscall.Syscall(syscall.SYS_READ, uintptr(c.fd),
|
||||||
|
uintptr(unsafe.Pointer(&packet[0])), uintptr(len(packet)))
|
||||||
|
if errn != 0 || res == 0 {
|
||||||
|
if errn != 0 {
|
||||||
|
c.err = errn
|
||||||
|
}
|
||||||
|
syscall.Close(c.fd)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
shandle(c, packet[:res])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
//+build !darwin,!freebsd,!linux
|
||||||
|
|
||||||
|
package shiny
|
||||||
|
|
||||||
|
func eventServe(net, addr string,
|
||||||
|
handle func(id int, data []byte, ctx interface{}) (send []byte, keepopen bool),
|
||||||
|
accept func(id int, addr string, wake func(), ctx interface{}) (send []byte, keepopen bool),
|
||||||
|
closed func(id int, err error, ctx interface{}),
|
||||||
|
ticker func(ctx interface{}) (keepopen bool),
|
||||||
|
context interface{}) error {
|
||||||
|
return compatServe(net, addr, handle, accept, closed, ticker, context)
|
||||||
|
}
|
Loading…
Reference in New Issue