Skip to content
Anssi Halmeaho edited this page Nov 27, 2020 · 2 revisions

stdrpc

Provides support for Remote Procedure Calls (RPC) in FunL. This provides way for FunL inter-process communication. RPC works in client-server model. Client calls remotely procedure execution in another process by name (string) and providing arguments for call. Server executes procedure call and replies return value to client. Client and server are defined by using opaque values (opaque:rserver and opaque:rproxy) so process can act as client and server simultaneously by using those values.

All values (arguments and return value) are encoded and decoded before/after going "to the wire". Values can be any serializable FunL values.

RPC mechanism in general has been (rightfully so) critized for trying to hide differences between local and external/distributed communication that really cannot be hidden (failure mode, delays). Because of this stdrpc is very explicit about using remote (inter-process) communication so that it's clearly different from calling functions/procedures locally.

Implementation of stdrpc is based on using HTTP as transport mechanism and JSON encoding/decoding. HTTP communication is RPC kind of already but stdrpc makes usage easier as HTTP details are hidden and encoding/decoding values is automatically done. Couple of basic assumptions in stdrpc design were:

  • there is no separate server process(es) needed (design target)
  • processes using stdrpc are implemented in FunL language or in Go

If runtime error happens during remote procedure execution then error is returned to client.

RPC servers can be implemented also with Go language, see Example of RPC server in Go

Types (rproxy and rserver)

Opaque value: rproxy

Client process uses rproxy for making RPC calls. Client creates rproxy value by giving address (string) of server to it.

Opaque value: rserver

Server process creates rserver by giving address in which server Goroutine listens incoming RPC requests (Goroutine is created to background for receiving RPC).

Services

new-server

Creates server value. Address to listen is given as argument (string).

type: procedure

Format:

call(stdrpc.new-server <address:string>) -> <list: is-ok error-description opaque:rserver>

Return value: list

register

Registers procedure/function (3rd argument) with name (2nd argument) to rserver (1st argument).

type: procedure

Format:

call(stdrpc.register <opaque:rserver> <name:string> <proc/func>) -> <list: is-ok error-description>

Return value: list

unregister

Unregisters procedure/function from rserver (1st argument). If no 2nd argument is given then all procedures/functions are unregistered from rserver. If 2nd argument is given (name, string) then corresponding procedure/function is unregistered.

type: procedure

Format:

call(stdrpc.unregister <opaque:rserver>) -> <list: is-ok error-description>
call(stdrpc.unregister <opaque:rserver> <name:string>) -> <list: is-ok error-description>

Return value: list

new-proxy

Creates rproxy for client. Address of corresponding server is given as argument (string).

type: procedure

Format:

call(stdrpc.new-proxy <address:string>) -> <opaque:rproxy>

Return value: opaque:rproxy

rcall

Client calls this to make remote procedure call. Related rproxy is given as 1st argument. Name of procedure (registered name) is given as 2nd argument. After that arguements for RPC are given as separate arguments.

type: procedure

Format:

call(stdrpc.rcall <opaque:rproxy> <name:string> <arg-1:value> <arg-2:value> ...) -> <list: is-ok error-description <return-value>>

Return value: list

close

Shuts down rserver. Ongoing remote procedure calls are executed to completion and after that rserver closes.

type: procedure

Format:

call(stdrpc.close <opaque:rserver>) -> <list: is-ok error-description>

Return value: list

Example

Example of using RPC:

Client implementation (rclient.fnl):

ns main

main = proc()
	import stdrpc

	px = call(stdrpc.new-proxy 'localhost:9901')
	list(
		call(stdrpc.rcall px 'remote-proc-1' 10 20)
		call(stdrpc.rcall px 'remote-proc-2' 10 20)
	)
end

endns

Server implementation (rserver.fnl):

ns main

rproc-1 = proc(p1 p2)
	_ = print(sprintf('1: received: %v, %v' p1 p2))
	plus(p1 p2)
end

rproc-2 = proc(p1 p2)
	_ = print(sprintf('2: received: %v, %v' p1 p2))
	mul(p1 p2)
end

just-waiting = proc()
	import stdtime
	call(proc()
		while( call(proc() _ = print('...waiting...') _ = call(stdtime.sleep 10) true end) 'none')
	end)
end

main = proc()
	import stdrpc
	import stddbc

	ok err server = call(stdrpc.new-server 'localhost:9901'):
	_ = call(stddbc.assert ok err)

	_ = call(stdrpc.register server 'remote-proc-1' rproc-1)
	_ = call(stdrpc.register server 'remote-proc-2' rproc-2)

	call(just-waiting)
end

endns

Running server:

./funla rserver.fnl

...waiting...
1: received: 10, 20
2: received: 10, 20
...waiting...

Running client:

./funla rclient.fnl

list(list(true, '', 30), list(true, '', 200))

To be implemented

Following things could be implemented in future:

  • support for secure communication (TLS)
  • options for proxy (map) -> time limit for RPC completion etc.
Clone this wiki locally