Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go client with common FFI. #135

Draft
wants to merge 1 commit into
base: ffi/dev_yuryf_glide_ffi
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading