Skip to content

Commit

Permalink
Go client with common FFI.
Browse files Browse the repository at this point in the history
Signed-off-by: Yury-Fridlyand <[email protected]>
  • Loading branch information
Yury-Fridlyand committed Mar 9, 2024
1 parent 0820f8a commit e759ed4
Show file tree
Hide file tree
Showing 9 changed files with 469 additions and 7 deletions.
2 changes: 1 addition & 1 deletion go/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ install-tools-go1.22.0: install-build-tools install-dev-tools-go1.22.0

install-tools: install-tools-go1.22.0

build: build-glide-core build-glide-client generate-protobuf
build: build-glide-client generate-protobuf
go build ./...

build-glide-core:
Expand Down
107 changes: 107 additions & 0 deletions go/api/base_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0

package api

// #cgo LDFLAGS: -L../target/release -lglide_rs
// #include "../lib.h"
//
// void successCallback(uintptr_t channelPtr, char *message);
// void failureCallback(uintptr_t channelPtr, char *errMessage, RequestErrorType errType);
import "C"

import (
"github.com/aws/glide-for-redis/go/glide/protobuf"
"google.golang.org/protobuf/proto"
"unsafe"
)

//export successCallback
func successCallback(channelPtr C.uintptr_t, cResponse *C.char) {
response := C.GoString(cResponse)
goChannelPointer := uintptr(channelPtr)
resultChannel := *(*chan payload)(unsafe.Pointer(goChannelPointer))
resultChannel <- payload{value: &response, error: nil}
}

//export failureCallback
func failureCallback(channelPtr C.uintptr_t, cErrorMessage *C.char, cErrorType C.RequestErrorType) {
goChannelPointer := uintptr(channelPtr)
resultChannel := *(*chan payload)(unsafe.Pointer(goChannelPointer))
resultChannel <- payload{value: nil, error: goError(cErrorType, cErrorMessage)}
}

type connectionRequestConverter interface {
toProtobuf() *protobuf.ConnectionRequest
}

type baseClient struct {
coreClient unsafe.Pointer
}

func createClient(converter connectionRequestConverter) (unsafe.Pointer, error) {
request := converter.toProtobuf()
msg, err := proto.Marshal(request)
if err != nil {
return nil, err
}

byteCount := len(msg)
requestBytes := C.CBytes(msg)
cResponse := (*C.struct_ConnectionResponse)(C.create_client_using_protobuf((*C.uchar)(requestBytes), C.uintptr_t(byteCount), (C.SuccessCallback)(unsafe.Pointer(C.successCallback)), (C.FailureCallback)(unsafe.Pointer(C.failureCallback))))
defer C.free_connection_response(cResponse)

cErr := cResponse.error_message
if cErr != nil {
return nil, goError(cResponse.error_type, cResponse.error_message)
}

return cResponse.conn_ptr, nil
}

// Close terminates the client by closing all associated resources.
func (client *baseClient) Close() error {
if client.coreClient == nil {
return &GlideError{"The glide client was not open. Either it was not initialized, or it was already closed."}
}

C.close_client(client.coreClient)
client.coreClient = nil
return nil
}

func (client *baseClient) CustomCommand(args []string) (interface{}, error) {
cArgs := toCStrings(args)
defer freeCStrings(cArgs)

resultChannel := make(chan payload)
resultChannelPtr := uintptr(unsafe.Pointer(&resultChannel))

requestType := C.uint32_t(customCommand)
C.command(client.coreClient, C.uintptr_t(resultChannelPtr), requestType, C.uintptr_t(len(args)), &cArgs[0])

payload := <-resultChannel
if payload.error != nil {
return nil, payload.error
}

if payload.value != nil {
return *payload.value, nil
}

return nil, nil
}

func toCStrings(args []string) []*C.char {
cArgs := make([]*C.char, len(args))
for i, arg := range args {
cString := C.CString(arg)
cArgs[i] = cString
}
return cArgs
}

func freeCStrings(cArgs []*C.char) {
for _, arg := range cArgs {
C.free(unsafe.Pointer(arg))
}
}
18 changes: 18 additions & 0 deletions go/api/cluster_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0

package api

// RedisClusterClient is a client used for connection to cluster Redis servers.
type RedisClusterClient struct {
baseClient
}

// CreateClusterClient creates a Redis client in cluster mode using the given [RedisClusterClientConfiguration].
func CreateClusterClient(config *RedisClusterClientConfiguration) (*RedisClusterClient, error) {
connPtr, err := createClient(config)
if err != nil {
return nil, err
}

return &RedisClusterClient{baseClient{connPtr}}, nil
}
51 changes: 51 additions & 0 deletions go/api/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0

package api

// #cgo LDFLAGS: -L../target/release -lglide_rs
// #include "../lib.h"
import "C"

type GlideError struct {
msg string
}

func (e GlideError) Error() string { return e.msg }

type RequestError struct {
msg string
}

func (e *RequestError) Error() string { return e.msg }

type ExecAbortError struct {
msg string
}

func (e *ExecAbortError) Error() string { return e.msg }

type TimeoutError struct {
msg string
}

func (e *TimeoutError) Error() string { return e.msg }

type DisconnectError struct {
msg string
}

func (e *DisconnectError) Error() string { return e.msg }

func goError(cErrorType C.RequestErrorType, cErrorMessage *C.char) error {
msg := C.GoString(cErrorMessage)
switch cErrorType {
case C.ExecAbort:
return &ExecAbortError{msg}
case C.Timeout:
return &TimeoutError{msg}
case C.Disconnect:
return &DisconnectError{msg}
default:
return &RequestError{msg}
}
}
99 changes: 99 additions & 0 deletions go/api/requests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0

package api

// #cgo LDFLAGS: -L../target/release -lglide_rs
// #include "../lib.h"
import "C"

type payload struct {
value *string
error error
}

type RequestType uint32

const (
_ = iota
customCommand RequestType = iota
getString
setString
ping
info
del
selectDB
configGet
configSet
configResetStat
configRewrite
clientGetName
clientGetRedir
clientId
clientInfo
clientKill
clientList
clientNoEvict
clientNoTouch
clientPause
clientReply
clientSetInfo
clientSetName
clientUnblock
clientUnpause
expire
hashSet
hashGet
hashDel
hashExists
mGet
mSet
incr
incrBy
decr
incrByFloat
decrBy
hashGetAll
hashMSet
hashMGet
hashIncrBy
hashIncrByFloat
lPush
lPop
rPush
rPop
lLen
lRem
lRange
lTrim
sAdd
sRem
sMembers
sCard
pExpireAt
pExpire
expireAt
exists
unlink
ttl
zAdd
zRem
zRange
zCard
zCount
zIncrBy
zScore
keyType
hLen
echo
zPopMin
strlen
lIndex
zPopMax
xRead
xAdd
xReadGroup
xAck
xTrim
xGroupCreate
xGroupDestroy
)
22 changes: 22 additions & 0 deletions go/api/standalone_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0
*/

package api

import "C"

// RedisClient is a client used for connection to standalone Redis servers.
type RedisClient struct {
baseClient
}

// CreateClient creates a Redis client in standalone mode using the given [RedisClientConfiguration].
func CreateClient(config *RedisClientConfiguration) (*RedisClient, error) {
connPtr, err := createClient(config)
if err != nil {
return nil, err
}

return &RedisClient{baseClient{connPtr}}, nil
}
75 changes: 75 additions & 0 deletions go/glide/glide.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0
*/
package glide

/*
#cgo LDFLAGS: -L../target/release -lglide_rs
#include "../lib.h"
void successCallback(uintptr_t channelPtr, char *message);
void failureCallback(uintptr_t channelPtr, char *errMessage, RequestErrorType errType);
*/
import "C"

import (
"fmt"
"github.com/aws/glide-for-redis/go/glide/protobuf"
"google.golang.org/protobuf/proto"
"unsafe"
)

type GlideRedisClient struct {
coreClient unsafe.Pointer
}

type payload struct {
value string
errMessage error
}

type RequestType uint32

type ErrorType uint32

const (
ClosingError = iota
RequestError
TimeoutError
ExecAbortError
ConnectionError
)

//export successCallback
func successCallback(channelPtr C.uintptr_t, message *C.char) {
// TODO: Implement when we implement the command logic
}

//export failureCallback
func failureCallback(channelPtr C.uintptr_t, errMessage *C.char, errType C.RequestErrorType) {
// TODO: Implement when we implement the command logic
}

func (glideRedisClient *GlideRedisClient) ConnectToRedis(request *protobuf.ConnectionRequest) error {
marshalledRequest, err := proto.Marshal(request)
if err != nil {
return fmt.Errorf("Failed to encode connection request: %v", err)
}
byteCount := len(marshalledRequest)
requestBytes := C.CBytes(marshalledRequest)
response := (*C.struct_ConnectionResponse)(C.create_client_using_protobuf((*C.uchar)(requestBytes), C.uintptr_t(byteCount), (C.SuccessCallback)(unsafe.Pointer(C.successCallback)), (C.FailureCallback)(unsafe.Pointer(C.failureCallback))))
defer C.free_connection_response(response)
if response.error_message != nil {
return fmt.Errorf(C.GoString(response.error_message))
}
glideRedisClient.coreClient = response.conn_ptr
return nil
}

func (glideRedisClient *GlideRedisClient) CloseClient() error {
if glideRedisClient.coreClient == nil {
return fmt.Errorf("Cannot close glide client before it has been created.")
}
C.close_client(glideRedisClient.coreClient)
return nil
}
Loading

0 comments on commit e759ed4

Please sign in to comment.