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

Implement a cancellable CallContext() method #44

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
119 changes: 81 additions & 38 deletions gorfc/gorfc.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ package gorfc

#cgo darwin CFLAGS: -Wall -O2 -Wno-uninitialized -Wcast-align
#cgo darwin CFLAGS: -DSAP_UC_is_wchar -DSAPwithUNICODE -D__NO_MATH_INLINES -DSAPwithTHREADS -DSAPonDARW
#cgo darwin CFLAGS: -fexceptions -funsigned-char -fno-strict-aliasing -fPIC -pthread -std=c17 -mmacosx-version-min=10.15
#cgo darwin CFLAGS: -fexceptions -funsigned-char -fno-strict-aliasing -fPIC -pthread -std=c17
#cgo darwin CFLAGS: -fno-omit-frame-pointer

#cgo darwin CFLAGS: -I/usr/local/sap/nwrfcsdk/include
Expand All @@ -58,7 +58,6 @@ package gorfc

#cgo darwin LDFLAGS: -O2 -g -pthread
#cgo darwin LDFLAGS: -stdlib=libc++
#cgo darwin LDFLAGS: -mmacosx-version-min=10.15

#include <sapnwrfc.h>

Expand All @@ -74,6 +73,8 @@ static unsigned GoStrlenU(SAP_UTF16 *str) {
import "C"

import (
"context"
"errors"
"fmt"
"reflect"
"runtime"
Expand Down Expand Up @@ -1140,8 +1141,41 @@ func (conn *Connection) GetFunctionDescription(goFuncName string) (goFuncDesc Fu
return wrapFunctionDescription(funcDesc)
}

// Call calls the given function with the given parameters and wraps the results returned.
func (conn *Connection) Call(goFuncName string, params interface{}) (result map[string]interface{}, err error) {
func setupParameter(params interface{}, funcDesc C.RFC_FUNCTION_DESC_HANDLE, funcCont C.RFC_FUNCTION_HANDLE) error {
paramsValue := reflect.ValueOf(params)
if paramsValue.Type().Kind() == reflect.Map {
hadrienk marked this conversation as resolved.
Show resolved Hide resolved
keys := paramsValue.MapKeys()
if len(keys) > 0 {
if keys[0].Kind() == reflect.String {
for _, nameValue := range keys {
fieldName := nameValue.String()
fieldValue := paramsValue.MapIndex(nameValue).Interface()
err := fillFunctionParameter(funcDesc, funcCont, fieldName, fieldValue)
if err != nil {
return err
}
}
} else {
return errors.New("could not fill parameters passed as map with non-string keys")
}
}
} else if paramsValue.Type().Kind() == reflect.Struct {
for i := 0; i < paramsValue.NumField(); i++ {
fieldName := paramsValue.Type().Field(i).Name
fieldValue := paramsValue.Field(i).Interface()

err := fillFunctionParameter(funcDesc, funcCont, fieldName, fieldValue)
if err != nil {
return err
}
}
} else {
return errors.New("parameters can only be passed as types map[string]interface{} or go-structures")
}
return nil
}

func (conn *Connection) rfcInvoke(goFuncName string, params interface{}) (result map[string]interface{}, err error) {
if !conn.alive {
return nil, goRfcError("Call() method requires an open connection", nil)
}
Expand All @@ -1161,52 +1195,23 @@ func (conn *Connection) Call(goFuncName string, params interface{}) (result map[
}
}

funcDesc := C.RfcGetFunctionDesc(conn.handle, funcName, &errorInfo)
var funcDesc C.RFC_FUNCTION_DESC_HANDLE = C.RfcGetFunctionDesc(conn.handle, funcName, &errorInfo)
if funcDesc == nil {
return result, rfcError(errorInfo, "Could not get function description for \"%v\"", funcName)
}

funcCont := C.RfcCreateFunction(funcDesc, &errorInfo)
var funcCont C.RFC_FUNCTION_HANDLE = C.RfcCreateFunction(funcDesc, &errorInfo)
if funcCont == nil {
return result, rfcError(errorInfo, "Could not create function")
}

defer C.RfcDestroyFunction(funcCont, nil)

paramsValue := reflect.ValueOf(params)
if paramsValue.Type().Kind() == reflect.Map {
keys := paramsValue.MapKeys()
if len(keys) > 0 {
if keys[0].Kind() == reflect.String {
for _, nameValue := range keys {
fieldName := nameValue.String()
fieldValue := paramsValue.MapIndex(nameValue).Interface()

err = fillFunctionParameter(funcDesc, funcCont, fieldName, fieldValue)
if err != nil {
return
}
}
} else {
return result, rfcError(errorInfo, "Could not fill parameters passed as map with non-string keys")
}
}
} else if paramsValue.Type().Kind() == reflect.Struct {
for i := 0; i < paramsValue.NumField(); i++ {
fieldName := paramsValue.Type().Field(i).Name
fieldValue := paramsValue.Field(i).Interface()

err = fillFunctionParameter(funcDesc, funcCont, fieldName, fieldValue)
if err != nil {
return
}
}
} else {
return result, rfcError(errorInfo, "Parameters can only be passed as types map[string]interface{} or go-structures")
err = setupParameter(params, funcDesc, funcCont)
if err != nil {
return
}

rc := C.RfcInvoke(conn.handle, funcCont, &errorInfo)

if rc != C.RFC_OK {
return result, rfcError(errorInfo, "Could not invoke function \"%v\"", goFuncName)
}
Expand All @@ -1216,3 +1221,41 @@ func (conn *Connection) Call(goFuncName string, params interface{}) (result map[
}
return wrapResult(funcDesc, funcCont, C.RFC_IMPORT, conn.rstrip)
}

func (conn *Connection) rfcCancel(goFuncName string) error {
var errorInfo C.RFC_ERROR_INFO
rc := C.RfcCancel(conn.handle, &errorInfo)
if rc != C.RFC_OK {
return rfcError(errorInfo, "Could not invoke function \"%v\"", goFuncName)
}
conn.alive = false
return nil
}

// Call calls the given function with the given parameters and wraps the results returned.
func (conn *Connection) Call(goFuncName string, params interface{}) (result map[string]interface{}, err error) {
return conn.CallContext(context.Background(), goFuncName, params)
}

// CallContext calls the given function with the given parameters and wraps the results returned.
func (conn *Connection) CallContext(ctx context.Context, goFuncName string, params interface{}) (result map[string]interface{}, err error) {
if ctx.Done() == nil {
return conn.rfcInvoke(goFuncName, params)
}

done := make(chan struct{})
go func() {
defer close(done)
result, err = conn.rfcInvoke(goFuncName, params)
done <- struct{}{}
}()

select {
case <-ctx.Done():
conn.alive = false
err = errors.Join(ctx.Err(), conn.rfcCancel(goFuncName), conn.Open())
hadrienk marked this conversation as resolved.
Show resolved Hide resolved
case <-done:
}

return
}
24 changes: 19 additions & 5 deletions gorfc/gorfc_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package gorfc

import (
"context"
"fmt"
"github.com/stretchr/testify/require"
"os"
"reflect"
"strconv"
Expand All @@ -14,19 +16,15 @@ import (
"github.com/sap/gorfc/gorfc/testutils"
)

//
// NW RFC Lib Version
//
func TestNWRFCLibVersion(t *testing.T) {
major, minor, patchlevel := GetNWRFCLibVersion()
assert.Equal(t, uint(7500), major) // adapt to your NW RFC Lib version
assert.Equal(t, uint(0), minor)
assert.Greater(t, patchlevel, uint(4))
}

//
// Connection Tests
//
func TestConnect(t *testing.T) {
fmt.Println("Connection test: Open and Close")
c, err := ConnectionFromParams(abapSystem())
Expand All @@ -36,7 +34,7 @@ func TestConnect(t *testing.T) {
assert.NotNil(t, c)
assert.Nil(t, err)
assert.True(t, c.Alive())
c.Close()
assert.NoError(t, c.Close())
assert.False(t, c.Alive())
}

Expand Down Expand Up @@ -361,6 +359,22 @@ func TestConfigParameter(t *testing.T) {
c.Close()
}

func TestCancelCall(t *testing.T) {
c, err := ConnectionFromParams(abapSystem())
require.Nil(t, err)

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_, err = c.CallContext(ctx, "RFC_PING_AND_WAIT", map[string]interface{}{
"SECONDS": 4,
})
assert.ErrorIs(t, err, context.DeadlineExceeded)

_, err = c.Call("RFC_PING", map[string]interface{}{})
assert.NoError(t, err)
assert.NoError(t, c.Close())
}

func TestInvalidParameterFunctionCall(t *testing.T) {
fmt.Println("STFC: Invalid RFM parameter")
c, err := ConnectionFromParams(abapSystem())
Expand Down