gearman-go/client/client.go

361 lines
8.3 KiB
Go
Raw Permalink Normal View History

// The client package helps developers connect to Gearmand, send
// jobs and fetch result.
package client
import (
2014-01-09 17:58:02 +08:00
"bufio"
2013-08-29 16:51:23 +08:00
"net"
"sync"
"time"
)
2015-07-10 20:30:35 +08:00
var (
DefaultTimeout time.Duration = time.Second
2015-07-10 20:30:35 +08:00
)
// One client connect to one server.
// Use Pool for multi-connections.
type Client struct {
2013-12-24 22:04:10 +08:00
sync.Mutex
2017-02-06 19:38:24 +08:00
net, addr string
innerHandler *responseHandlerMap
in chan *Response
conn net.Conn
rw *bufio.ReadWriter
2013-08-29 16:51:23 +08:00
ResponseTimeout time.Duration // response timeout for do()
2013-08-30 11:20:51 +08:00
ErrorHandler ErrorHandler
}
2016-05-06 18:00:58 +08:00
type responseHandlerMap struct {
sync.Mutex
holder map[string]handledResponse
}
type handledResponse struct {
internal ResponseHandler // internal handler, always non-nil
external ResponseHandler // handler passed in from (*Client).Do, sometimes nil
2016-05-06 18:00:58 +08:00
}
func newResponseHandlerMap() *responseHandlerMap {
return &responseHandlerMap{holder: make(map[string]handledResponse, queueSize)}
2016-05-06 18:00:58 +08:00
}
func (r *responseHandlerMap) remove(key string) {
r.Lock()
delete(r.holder, key)
r.Unlock()
}
func (r *responseHandlerMap) getAndRemove(key string) (handledResponse, bool) {
r.Lock()
2016-05-06 18:00:58 +08:00
rh, b := r.holder[key]
delete(r.holder, key)
r.Unlock()
2016-05-06 18:00:58 +08:00
return rh, b
}
func (r *responseHandlerMap) putWithExternalHandler(key string, internal, external ResponseHandler) {
2016-05-06 18:00:58 +08:00
r.Lock()
r.holder[key] = handledResponse{internal: internal, external: external}
2016-05-06 18:00:58 +08:00
r.Unlock()
}
func (r *responseHandlerMap) put(key string, rh ResponseHandler) {
r.putWithExternalHandler(key, rh, nil)
}
// New returns a client.
func New(network, addr string) (client *Client, err error) {
2013-08-29 16:51:23 +08:00
client = &Client{
2015-07-10 20:30:35 +08:00
net: network,
addr: addr,
2016-05-06 18:00:58 +08:00
innerHandler: newResponseHandlerMap(),
2015-07-10 20:30:35 +08:00
in: make(chan *Response, queueSize),
ResponseTimeout: DefaultTimeout,
2013-08-29 16:51:23 +08:00
}
client.conn, err = net.Dial(client.net, client.addr)
2013-08-30 18:01:10 +08:00
if err != nil {
return
}
2014-01-09 16:16:34 +08:00
client.rw = bufio.NewReadWriter(bufio.NewReader(client.conn),
bufio.NewWriter(client.conn))
go client.readLoop()
go client.processLoop()
2013-08-29 16:51:23 +08:00
return
2013-01-14 17:59:48 +08:00
}
2013-08-29 16:51:23 +08:00
func (client *Client) write(req *request) (err error) {
var n int
buf := req.Encode()
for i := 0; i < len(buf); i += n {
2014-01-09 16:16:34 +08:00
n, err = client.rw.Write(buf[i:])
2013-08-29 16:51:23 +08:00
if err != nil {
return
}
}
2014-01-09 16:16:34 +08:00
return client.rw.Flush()
2013-01-14 17:59:48 +08:00
}
2013-08-29 16:51:23 +08:00
func (client *Client) read(length int) (data []byte, err error) {
n := 0
buf := getBuffer(bufferSize)
2013-08-29 16:51:23 +08:00
// read until data can be unpacked
for i := length; i > 0 || len(data) < minPacketLength; i -= n {
2014-01-09 16:16:34 +08:00
if n, err = client.rw.Read(buf); err != nil {
2013-08-29 16:51:23 +08:00
return
}
data = append(data, buf[0:n]...)
if n < bufferSize {
2013-08-29 16:51:23 +08:00
break
}
}
return
2013-01-14 17:59:48 +08:00
}
2013-08-29 16:51:23 +08:00
func (client *Client) readLoop() {
defer close(client.in)
var data, leftdata []byte
2013-08-29 16:51:23 +08:00
var err error
var resp *Response
2014-01-09 16:16:34 +08:00
ReadLoop:
2014-03-03 11:40:42 +08:00
for client.conn != nil {
if data, err = client.read(bufferSize); err != nil {
2014-03-03 14:45:35 +08:00
if opErr, ok := err.(*net.OpError); ok {
if opErr.Timeout() {
client.err(err)
}
if opErr.Temporary() {
continue
}
break
}
2014-03-03 14:45:35 +08:00
client.err(err)
// If it is unexpected error and the connection wasn't
// closed by Gearmand, the client should close the conection
// and reconnect to job server.
client.Close()
client.conn, err = net.Dial(client.net, client.addr)
if err != nil {
client.err(err)
2013-08-29 16:51:23 +08:00
break
}
2014-01-09 16:16:34 +08:00
client.rw = bufio.NewReadWriter(bufio.NewReader(client.conn),
bufio.NewWriter(client.conn))
2013-08-29 16:51:23 +08:00
continue
}
if len(leftdata) > 0 { // some data left for processing
2013-08-29 18:08:05 +08:00
data = append(leftdata, data...)
leftdata = nil
2013-08-29 16:51:23 +08:00
}
2014-01-09 16:16:34 +08:00
for {
l := len(data)
if l < minPacketLength { // not enough data
leftdata = data
continue ReadLoop
}
if resp, l, err = decodeResponse(data); err != nil {
leftdata = data[l:]
continue ReadLoop
} else {
client.in <- resp
}
data = data[l:]
if len(data) > 0 {
continue
}
break
2013-08-29 16:51:23 +08:00
}
}
}
func (client *Client) processLoop() {
rhandlers := map[string]ResponseHandler{}
for resp := range client.in {
switch resp.DataType {
case dtError:
2017-02-06 19:38:24 +08:00
client.err(getError(resp.Data))
case dtStatusRes:
client.handleInner("s"+resp.Handle, resp, nil)
case dtJobCreated:
client.handleInner("c", resp, rhandlers)
case dtEchoRes:
client.handleInner("e", resp, nil)
2014-01-09 16:16:34 +08:00
case dtWorkData, dtWorkWarning, dtWorkStatus:
if cb := rhandlers[resp.Handle]; cb != nil {
cb(resp)
}
2014-01-09 16:16:34 +08:00
case dtWorkComplete, dtWorkFail, dtWorkException:
if cb := rhandlers[resp.Handle]; cb != nil {
cb(resp)
delete(rhandlers, resp.Handle)
}
}
}
}
2013-08-29 16:51:23 +08:00
func (client *Client) err(e error) {
if client.ErrorHandler != nil {
client.ErrorHandler(e)
}
}
func (client *Client) handleInner(key string, resp *Response, rhandlers map[string]ResponseHandler) {
if h, ok := client.innerHandler.getAndRemove(key); ok {
if h.external != nil && resp.Handle != "" {
rhandlers[resp.Handle] = h.external
}
h.internal(resp)
2013-08-29 16:51:23 +08:00
}
2013-01-14 17:59:48 +08:00
}
type handleOrError struct {
handle string
2015-07-10 20:30:35 +08:00
err error
}
2013-01-15 17:55:44 +08:00
func (client *Client) do(funcname string, data []byte,
2022-04-06 06:11:45 +08:00
flag uint32, h ResponseHandler, id string) (handle string, err error) {
if len(id) == 0 {
return "", ErrInvalidId
}
2014-03-03 11:40:42 +08:00
if client.conn == nil {
return "", ErrLostConn
}
var result = make(chan handleOrError, 1)
client.Lock()
defer client.Unlock()
client.innerHandler.putWithExternalHandler("c", func(resp *Response) {
if resp.DataType == dtError {
err = getError(resp.Data)
result <- handleOrError{"", err}
2013-08-30 11:20:51 +08:00
return
}
2013-08-29 16:51:23 +08:00
handle = resp.Handle
result <- handleOrError{handle, nil}
}, h)
2013-09-22 22:58:22 +08:00
req := getJob(id, []byte(funcname), data)
req.DataType = flag
2014-08-21 00:27:32 +08:00
if err = client.write(req); err != nil {
2016-05-06 18:00:58 +08:00
client.innerHandler.remove("c")
2014-08-21 00:27:32 +08:00
return
}
var timer = time.After(client.ResponseTimeout)
select {
case ret := <-result:
return ret.handle, ret.err
case <-timer:
2016-05-06 18:00:58 +08:00
client.innerHandler.remove("c")
return "", ErrLostConn
}
2013-08-29 16:51:23 +08:00
return
2013-01-14 17:59:48 +08:00
}
// Call the function and get a response.
// flag can be set to: JobLow, JobNormal and JobHigh
2013-01-15 17:55:44 +08:00
func (client *Client) Do(funcname string, data []byte,
2013-08-30 11:20:51 +08:00
flag byte, h ResponseHandler) (handle string, err error) {
2022-04-06 06:11:45 +08:00
handle, err = client.DoWithId(funcname, data, flag, h, IdGen.Id())
2013-08-29 16:51:23 +08:00
return
2013-01-14 17:59:48 +08:00
}
// Call the function in background, no response needed.
// flag can be set to: JobLow, JobNormal and JobHigh
2013-01-15 17:55:44 +08:00
func (client *Client) DoBg(funcname string, data []byte,
2013-08-30 11:20:51 +08:00
flag byte) (handle string, err error) {
2022-04-06 06:11:45 +08:00
handle, err = client.DoBgWithId(funcname, data, flag, IdGen.Id())
2013-08-29 16:51:23 +08:00
return
}
// Status gets job status from job server.
2013-08-30 11:20:51 +08:00
func (client *Client) Status(handle string) (status *Status, err error) {
2014-03-03 11:40:42 +08:00
if client.conn == nil {
return nil, ErrLostConn
}
2013-12-24 22:04:10 +08:00
var mutex sync.Mutex
mutex.Lock()
2016-05-06 18:00:58 +08:00
client.innerHandler.put("s"+handle, func(resp *Response) {
2014-03-11 10:03:00 +08:00
defer mutex.Unlock()
2013-08-29 18:08:05 +08:00
var err error
2014-01-09 16:16:34 +08:00
status, err = resp._status()
2013-08-29 18:08:05 +08:00
if err != nil {
client.err(err)
}
2016-05-06 18:00:58 +08:00
})
2013-09-22 22:58:22 +08:00
req := getRequest()
req.DataType = dtGetStatus
2013-09-22 22:58:22 +08:00
req.Data = []byte(handle)
client.write(req)
2013-12-24 22:04:10 +08:00
mutex.Lock()
2013-08-29 16:51:23 +08:00
return
}
// Echo.
2013-08-29 18:08:05 +08:00
func (client *Client) Echo(data []byte) (echo []byte, err error) {
2014-03-03 11:40:42 +08:00
if client.conn == nil {
return nil, ErrLostConn
}
2013-12-24 22:04:10 +08:00
var mutex sync.Mutex
mutex.Lock()
2016-05-06 18:00:58 +08:00
client.innerHandler.put("e", func(resp *Response) {
2013-08-29 18:08:05 +08:00
echo = resp.Data
2013-12-24 22:04:10 +08:00
mutex.Unlock()
2016-05-06 18:00:58 +08:00
})
2013-09-22 22:58:22 +08:00
req := getRequest()
req.DataType = dtEchoReq
2013-09-22 22:58:22 +08:00
req.Data = data
2013-12-24 22:04:10 +08:00
client.write(req)
mutex.Lock()
2013-08-29 16:51:23 +08:00
return
}
// Close connection
func (client *Client) Close() (err error) {
client.Lock()
defer client.Unlock()
if client.conn != nil {
err = client.conn.Close()
client.conn = nil
}
return
}
2022-04-06 06:11:45 +08:00
// Call the function and get a response.
// flag can be set to: JobLow, JobNormal and JobHigh
func (client *Client) DoWithId(funcname string, data []byte,
flag byte, h ResponseHandler, id string) (handle string, err error) {
var datatype uint32
switch flag {
case JobLow:
datatype = dtSubmitJobLow
case JobHigh:
datatype = dtSubmitJobHigh
default:
datatype = dtSubmitJob
}
handle, err = client.do(funcname, data, datatype, h, id)
return
}
// Call the function in background, no response needed.
// flag can be set to: JobLow, JobNormal and JobHigh
func (client *Client) DoBgWithId(funcname string, data []byte,
flag byte, id string) (handle string, err error) {
if client.conn == nil {
return "", ErrLostConn
}
var datatype uint32
switch flag {
case JobLow:
datatype = dtSubmitJobLowBg
case JobHigh:
datatype = dtSubmitJobHighBg
default:
datatype = dtSubmitJobBg
}
handle, err = client.do(funcname, data, datatype, nil, id)
return
}