Skip to content

Commit

Permalink
add some docstrings and test for memory leaks
Browse files Browse the repository at this point in the history
  • Loading branch information
thomvet committed Dec 4, 2024
1 parent a0da404 commit 93ad299
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 16 deletions.
16 changes: 8 additions & 8 deletions src/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ function UA_ClientConfig_setAuthenticationUsername(config, username, password)
end
end

# /* Connect to the server. First a SecureChannel is opened, then a Session. The
# * client configuration restricts the SecureChannel selection and contains the
# * UserIdentityToken for the Session.
# *
# * @param client to use
# * @param endpointURL to connect (for example "opc.tcp://localhost:4840")
# * @return Indicates whether the operation succeeded or returns an error code */
##TODO: ADD DOCSTRING
"""
```
UA_Client_connect(client::Ptr{UA_Client}, endpointurl::AbstractString)::UA_StatusCode
```
connect the `client` to the server with `endpointurl`. This is an anonymous connection, i.e.,
no username or password are used (some servers do not allow this).
"""
function UA_Client_connect(client, endpointUrl)
cc = UA_Client_getConfig(client)
cc.noSession = false
Expand Down
38 changes: 32 additions & 6 deletions src/highlevel_client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,34 @@ mutable struct JUA_ClientConfig <: AbstractOpen62541Wrapper
end

const JUA_ClientConfig_setDefault = UA_ClientConfig_setDefault
const JUA_Client_connect = UA_Client_connect
const JUA_Client_connectUsername = UA_Client_connectUsername
"""
```
JUA_Client_connect(client::JUA_Client, endpointurl::AbstractString)::UA_StatusCode
```
connect the `client` to the server with `endpointurl`. This is an anonymous connection, i.e.,
no username or password are used (some servers do not allow this).
"""
function JUA_Client_connect(client::JUA_Client, endpointurl::AbstractString)
UA_Client_connect(client, endpointurl)
end

"""
```
JUA_Client_connectUsername(client::JUA_Client, endpointurl::AbstractString,
username::AbstractString, password::AbstractString)::UA_StatusCode
```
connects the `client` to the server with endpoint URL `endpointurl` and supplies
`username` and `password` as login credentials.
"""
function JUA_Client_connectUsername(client::JUA_Client, endpointurl::AbstractString,
username::AbstractString, password::AbstractString)
UA_Client_connectUsername(client, endpointurl, username, password)
end

const JUA_Client_disconnect = UA_Client_disconnect

#Add node functions
Expand Down Expand Up @@ -275,6 +301,7 @@ function JUA_Client_call(
input_variants.ptr, Ref(UInt64(length(outputarguments))), ref)

UA_BrowseRequest_delete(breq)
UA_BrowseResponse_delete(bresp)
UA_Variant_Array_delete(input_variants)

if !isnothing(e)
Expand All @@ -283,12 +310,11 @@ function JUA_Client_call(

if sc != UA_STATUSCODE_GOOD
throw(ClientServiceRequestError("Calling method via Client API failed with statuscode \"$(UA_StatusCode_name_print(sc))\"."))
UA_Variant_Array_delete(input_variants)
else
arr_output = UA_Array(ref[], length(outputarguments))
r = __get_juliavalues_from_variant.(arr_output, Any)
arr_output2 = UA_Array(ref[], length(outputarguments))
r = __get_juliavalues_from_variant.(arr_output2, Any)

#TODO: check that we clean up all the necessary variables.
UA_Variant_Array_delete(arr_output)
if length(outputarguments) == 1
return r[1]
else
Expand Down
134 changes: 134 additions & 0 deletions test/client_memoryleaks.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using Open62541
using Test

function meminfo_julia()
Float64(Sys.maxrss() / 2^20) #gives memory use of the whole julia process in MBs
end

#Client Call

using Distributed
Distributed.addprocs(1) # Add a single worker process to run the server

Distributed.@everywhere begin
using Open62541
using Test
using Pkg
using Pkg.BinaryPlatforms

#create methods that will be used later
function simple_two_in_two_out_mixed_type(name, number)
out1 = "Hello " * name * "."
out2 = number * number
return (out1, out2)
end

function c5(server, sessionId, sessionHandle, methodId, methodContext, objectId,
objectContext, inputSize, input, outputSize, output)
arr_input = UA_Array(input, Int64(inputSize))
arr_output = UA_Array(output, Int64(outputSize))
input_julia = Open62541.__get_juliavalues_from_variant.(arr_input, Any)
output_julia = simple_two_in_two_out_mixed_type(input_julia...)
if !isa(output_julia, Tuple)
output_julia = (output_julia,)
end
for i in 1:outputSize
j = JUA_Variant(output_julia[i])
UA_Variant_copy(Open62541.Jpointer(j), arr_output[i])
end
return UA_STATUSCODE_GOOD
end
end

Distributed.@spawnat Distributed.workers()[end] begin
#prepare method callbacks
@static if !Sys.isapple() || platform_key_abi().tags["arch"] != "aarch64"
w5 = UA_MethodCallback_wrap(simple_two_in_two_out_mixed_type)
m5 = UA_MethodCallback_generate(w5)
else #we are on Apple Silicon and can't use a closure in @cfunction, have to do MUCH more work.
m5 = @cfunction(c5, UA_StatusCode,
(Ptr{UA_Server}, Ptr{UA_NodeId}, Ptr{Cvoid},
Ptr{UA_NodeId}, Ptr{Cvoid}, Ptr{UA_NodeId}, Ptr{Cvoid},
Csize_t, Ptr{UA_Variant}, Csize_t, Ptr{UA_Variant}))
end

#configure server
server = JUA_Server()
retval0 = JUA_ServerConfig_setMinimalCustomBuffer(JUA_ServerConfig(server),
4842, C_NULL, 0, 0)

#prepare method attributes
attr5 = JUA_MethodAttributes(description = "Simple Two in Two Out - Mixed Types",
displayname = "Simple Two in Two Out - Mixed Types",
executable = true,
userexecutable = true)

#prepare method nodeids
methodid5 = JUA_NodeId(1, 62545)

parentnodeid = JUA_NodeId(0, UA_NS0ID_OBJECTSFOLDER)
parentreferencenodeid = JUA_NodeId(0, UA_NS0ID_HASCOMPONENT)

#prepare browsenames
browsename5 = JUA_QualifiedName(1, "Simple Two in Two Out Mixed Type")

#prepare input arguments
j3 = JUA_Argument("examplestring", name = "Name", description = "Number")
j4 = JUA_Argument(25, name = "Number", description = "Number")
twoinputarg_mixed = [j3, j4]

#prepare output arguments
j3 = JUA_Argument("examplestring", name = "Name", description = "Name")
j4 = JUA_Argument(25, name = "Number", description = "Number")
twooutputarg_mixed = [j3, j4]

#add the nodes
retval5 = JUA_Server_addNode(server, methodid5, parentnodeid, parentreferencenodeid,
browsename5, attr5, m5, twoinputarg_mixed, twooutputarg_mixed,
JUA_NodeId(), JUA_NodeId())

# Start up the server
Distributed.@spawnat Distributed.workers()[end] redirect_stderr() # Turn off all error messages
println("Starting up the server...")
JUA_Server_runUntilInterrupt(server)
end


client = JUA_Client()
JUA_ClientConfig_setDefault(JUA_ClientConfig(client))
max_duration = 90.0 # Maximum waiting time for server startup
sleep_time = 3.0 # Sleep time in seconds between each connection trial
let trial
trial = 0
while trial < max_duration / sleep_time
retval3 = JUA_Client_connect(client, "opc.tcp://localhost:4842")
if retval3 == UA_STATUSCODE_GOOD
println("Connection established.")
break
end
sleep(sleep_time)
trial = trial + 1
end
@test trial < max_duration / sleep_time # Check if maximum number of trials has been exceeded
end

#calling the method node with high level interface
methodid5 = JUA_NodeId(1, 62545)
parentnodeid = JUA_NodeId(0, UA_NS0ID_OBJECTSFOLDER)
two_inputs_mixed = ("Claudia", 25)

#the actual memory leak tests
mem_start = meminfo_julia()
for i in 1:1_000_000
response5 = JUA_Client_call(client, parentnodeid, methodid5, two_inputs_mixed)
end
GC.gc()
mem_end = meminfo_julia()
@test (mem_end - mem_start) < 100.0


# Disconnect client & kill the server
JUA_Client_disconnect(client)
println("Ungracefully kill server process...")
Distributed.interrupt(Distributed.workers()[end])
Distributed.rmprocs(Distributed.workers()[end]; waitfor = 0)
10 changes: 8 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,21 @@ end
end

if MEMLEAK == true
@safetestset "Memory leaks" begin
include("memoryleaks.jl")
@safetestset "Server Memory leaks" begin
include("server_memoryleaks.jl")
end
end

#Testsets below here use Distributed; normal testsets required
# !!! Leakage of variables must be assessed manually. !!!
#see: https://github.com/YingboMa/SafeTestsets.jl/issues/13

if MEMLEAK == true
@testset "Client Memory Leaks" begin
include("client_memoryleaks.jl")
end
end

@testset "Simple Server/Client" begin
include("client_simple.jl")
end
Expand Down
File renamed without changes.

0 comments on commit 93ad299

Please sign in to comment.