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

Use macros to define messages rather than eval #45

Merged
merged 22 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c6aabb1
refactor(msg): use macros to define messages rather than eval (WIP)
mchitre Jan 23, 2024
a3c5363
refactor: improve message implementation
mchitre Jan 28, 2024
d64b36a
fix: register messages defined in Fjage
mchitre Jan 29, 2024
f3eb9cb
style: cosmetic improvements to code
mchitre Jan 29, 2024
630f264
refactor: improve GenericMessages and JSONification
mchitre Jan 29, 2024
d429569
test: fix test cases and cosmetic improvements
mchitre Jan 29, 2024
02fb6ba
refactor: make invalid properties for messages throw errors unless in…
mchitre Jan 31, 2024
1b2a489
refactor: drop unnecessary property handling code
mchitre Jan 31, 2024
12c3fe9
test: fix fjage version for testing
mchitre Jan 31, 2024
eee6eef
fix(msg): drop eval for performative and let it be interpolated in
mchitre Feb 1, 2024
fe5470a
test: add test case that uses performative in message definition
mchitre Feb 1, 2024
51bcd26
fix(msg): only assume Fjage symbol to be in scope of caller
mchitre Feb 1, 2024
7a15935
feat(msg): improve pretty printing
mchitre Feb 1, 2024
bba1083
doc: update docs to reflect new message definition API
mchitre Feb 1, 2024
993e261
refactor: Escape more narrowly in @message
ettersi Feb 2, 2024
030e6c8
fix(kwdef): Copy over Base.isexpr
ettersi Feb 4, 2024
868f916
fix(container): update container to use new message format
mchitre Feb 4, 2024
0088453
fix(msg): handle NaNs, inheritance and fix readonly property in Param…
mchitre Feb 4, 2024
55c290e
refactor(msg): use _Concrete prefix and use applicable() instead of t…
mchitre Feb 5, 2024
9af5a95
refactor: replace reporterror with logerror
mchitre Feb 5, 2024
80bdf6c
fix(kwdef): Delete @show that was added for debugging
ettersi Feb 7, 2024
7c5afd6
docs(kwdef): xref PR
ettersi Feb 7, 2024
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
102 changes: 95 additions & 7 deletions docs/src/msg.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,106 @@
# Messages

Messages are dictionary-like containers that carry information between agents.
Keys can be accessed on messages using the property notation (`msg.key`), and
keys that are absent yield `nothing`. When interacting with Java/Groovy agents,
messages are mapped to Java/Groovy message classes with fields with the same
name as the keys in the message.
Messages are data containers that carry information between agents. When
interacting with Java/Groovy agents, messages are mapped to Java/Groovy
message classes with fields with the same name as the keys in the message.

Message types are defined using the `MessageClass` function. For example:
Message types are defined using the `@message` macro. For example:
```julia
const ShellExecReq = MessageClass(@__MODULE__, "org.arl.fjage.shell.ShellExecReq")
@message "org.arl.fjage.shell.ShellExecReq" struct ShellExecReq
cmd::Union{String,Nothing} = nothing
script::Union{String,Nothing} = nothing
args::Vector{String} = String[]
ans::Bool = false
end
```
defines a `ShellExecReq` message type that maps to a Java class with the
package `org.arl.fjage.shell.ShellExecReq`.

All messages are mutable. The `@message` macro also automatically adds a few fields:

- `performative::Symbol`
- `messageID::String`
- `inReplyTo::String`
- `sender::AgentID`
- `recipient::AgentID`
- `sentAt::Int64`

Messages can subtype other messages:
```julia
julia> abstract type MyAbstractMessage <: Message end

julia> @message "org.arl.fjage.demo.MyConcreteMessage" struct MyConcreteMessage <: MyAbstractMessage
a::Int
end

julia> MyConcreteMessage(a=1)
MyConcreteMessage:INFORM[a:1]

julia> MyConcreteMessage(a=1) isa MyAbstractMessage
true
```

It is also possible to have a concrete message type that can also be a supertype
of another message:
```julia
julia> abstract type SomeMessage <: Message end

julia> @message "org.arl.fjage.demo.SomeMessage" struct SomeMessage <: SomeMessage
a::Int
end

julia> @message "org.arl.fjage.demo.SomeExtMessage" struct SomeExtMessage <: SomeMessage
a::Int
b::Int
end

julia> SomeMessage(a=1) isa SomeMessage
true

julia> SomeExtMessage(a=1, b=2) isa SomeMessage
true
```

Performatives are guessed automatically based on message classname. By default,
the performative is `Performative.INFORM`. If a message classname ends with a
`Req`, the default performative changes to `Performative.REQUEST`. Performatives
may be overridden at declaration or at construction (and are mutable):
```julia
julia> @message "org.arl.fjage.demo.SomeReq" struct SomeReq end;
julia> @message "org.arl.fjage.demo.SomeRsp" Performative.AGREE struct SomeRsp end;

julia> SomeReq().performative
:REQUEST

julia> SomeRsp().performative
:AGREE

julia> SomeRsp(performative=Performative.INFORM).performative
:INFORM
```

When strict typing is not required, one can use the dictionary-like
`GenericMessage` message type:
```julia
julia> msg = GenericMessage("org.arl.fjage.demo.DynamicMessage")
DynamicMessage:INFORM

julia> msg.a = 1
1

julia> msg.b = "xyz"
"xyz"

julia> msg
DynamicMessage:INFORM[a:1 b:"xyz"]

julia> classname(msg)
"org.arl.fjage.demo.DynamicMessage"

julia> msg isa GenericMessage
true
```

## API

```@autodocs
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<dependency>
<groupId>com.github.org-arl</groupId>
<artifactId>fjage</artifactId>
<version>1.10.0-SNAPSHOT</version>
<version>1.12.2</version>
</dependency>
</dependencies>
</project>
4 changes: 4 additions & 0 deletions src/Fjage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ include("container.jl")
include("fsm.jl")
include("coroutine_behavior.jl")

function __init__()
registermessages()
end

end
7 changes: 6 additions & 1 deletion src/const.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ module Services
end

"Shell command execution request message."
const ShellExecReq = MessageClass(@__MODULE__, "org.arl.fjage.shell.ShellExecReq")
@message "org.arl.fjage.shell.ShellExecReq" struct ShellExecReq
cmd::Union{String,Nothing} = nothing
script::Union{String,Nothing} = nothing
args::Vector{String} = String[]
ans::Bool = false
end
72 changes: 41 additions & 31 deletions src/container.jl
Original file line number Diff line number Diff line change
Expand Up @@ -673,13 +673,13 @@ function _deliver(c::SlaveContainer, msg::Message, relay::Bool)
if msg.recipient.name ∈ keys(c.agents)
_deliver(c.agents[msg.recipient.name], msg)
elseif relay
_prepare!(msg)
clazz, data = _prepare(msg)
json = JSON.json(Dict(
"action" => "send",
"relay" => true,
"message" => Dict(
"clazz" => msg.__clazz__,
"data" => msg.__data__
:action => :send,
:relay => true,
:message => Dict(
:clazz => clazz,
:data => data
)
))
try
Expand All @@ -701,22 +701,32 @@ _deliver(c::SlaveContainer, msg::Message) = _deliver(c, msg, true)

### stacktrace pretty printing & auto-reconnection

function reporterror(src, ex)
fname = basename(@__FILE__)
bt = String[]
for s ∈ stacktrace(catch_backtrace())
push!(bt, " $s")
basename(string(s.file)) == fname && s.func == :run && break
end
bts = join(bt, '\n')
if src === nothing
@error "$(ex)\n Stack trace:\n$(bts)"
else
@error "[$(src)] $(ex)\n Stack trace:\n$(bts)"
"""
logerror(f::Function)
logerror(f::Function, src)

Run function `f()` and log any errors that occur.
"""
function logerror(f::Function, src=nothing)
try
f()
catch ex
logerror(ex, src)
end
end

reporterror(ex) = reporterror(nothing, ex)
"""
logerror(err::Exception)
logerror(err::Exception, src)

Log error `err` with a simple stack trace.
"""
function logerror(ex::Exception, src=nothing)
io = IOBuffer()
src === nothing || print(io, "[$src] ")
Base.showerror(IOContext(io, :limit => true), ex, Base.catch_backtrace())
@error String(take!(io))
end

reconnect(c::StandaloneContainer, ex) = false
function reconnect(c::SlaveContainer, ex)
Expand Down Expand Up @@ -1443,7 +1453,7 @@ function action(b::OneShotBehavior)
b.action === nothing || _mutex_call(b.action, b.agent, b)
b.onend === nothing || _mutex_call(b.onend, b.agent, b)
catch ex
reconnect(container(b.agent), ex) || reporterror(b.agent, ex)
reconnect(container(b.agent), ex) || logerror(ex, b.agent)
end
b.done = true
delete!(b.agent._behaviors, b)
Expand Down Expand Up @@ -1496,7 +1506,7 @@ function action(b::CyclicBehavior)
try
b.action === nothing || _mutex_call(b.action, b.agent, b)
catch ex
reconnect(container(b.agent), ex) || reporterror(b.agent, ex)
reconnect(container(b.agent), ex) || logerror(ex, b.agent)
end
yield()
else
Expand All @@ -1505,7 +1515,7 @@ function action(b::CyclicBehavior)
end
b.onend === nothing || _mutex_call(b.onend, b.agent, b)
catch ex
reconnect(container(b.agent), ex) || reporterror(b.agent, ex)
reconnect(container(b.agent), ex) || logerror(ex, b.agent)
end
b.done = true
delete!(b.agent._behaviors, b)
Expand Down Expand Up @@ -1594,7 +1604,7 @@ function action(b::WakerBehavior)
end
b.onend === nothing || _mutex_call(b.onend, b.agent, b)
catch ex
reconnect(container(b.agent), ex) || reporterror(b.agent, ex)
reconnect(container(b.agent), ex) || logerror(ex, b.agent)
end
b.done = true
delete!(b.agent._behaviors, b)
Expand Down Expand Up @@ -1666,12 +1676,12 @@ function action(b::TickerBehavior)
try
b.done || b.action === nothing || _mutex_call(b.action, b.agent, b)
catch ex
reconnect(container(b.agent), ex) || reporterror(b.agent, ex)
reconnect(container(b.agent), ex) || logerror(ex, b.agent)
end
end
b.onend === nothing || _mutex_call(b.onend, b.agent, b)
catch ex
reconnect(container(b.agent), ex) || reporterror(b.agent, ex)
reconnect(container(b.agent), ex) || logerror(ex, b.agent)
end
b.done = true
delete!(b.agent._behaviors, b)
Expand Down Expand Up @@ -1733,12 +1743,12 @@ function action(b::PoissonBehavior)
try
b.done || b.action === nothing || _mutex_call(b.action, b.agent, b)
catch ex
reconnect(container(b.agent), ex) || reporterror(b.agent, ex)
reconnect(container(b.agent), ex) || logerror(ex, b.agent)
end
end
b.onend === nothing || _mutex_call(b.onend, b.agent, b)
catch ex
reconnect(container(b.agent), ex) || reporterror(b.agent, ex)
reconnect(container(b.agent), ex) || logerror(ex, b.agent)
end
b.done = true
delete!(b.agent._behaviors, b)
Expand Down Expand Up @@ -1814,12 +1824,12 @@ function action(b::MessageBehavior)
msg = take!(ch)
msg === nothing || b.action === nothing || _mutex_call(b.action, b.agent, b, msg)
catch ex
reconnect(container(b.agent), ex) || reporterror(b.agent, ex)
reconnect(container(b.agent), ex) || logerror(ex, b.agent)
end
end
b.onend === nothing || _mutex_call(b.onend, b.agent, b)
catch ex
reconnect(container(b.agent), ex) || reporterror(b.agent, ex)
reconnect(container(b.agent), ex) || logerror(ex, b.agent)
finally
_dont_listen(b.agent, ch)
close(ch)
Expand Down Expand Up @@ -2056,10 +2066,10 @@ function _paramreq_action(a::Agent, b::MessageBehavior, msg::ParameterReq)
end
end
catch ex
reconnect(container(a), ex) || reporterror(a, ex)
reconnect(container(a), ex) || logerror(ex, a)
end
end
rmsg = ParameterRsp(perf=Performative.INFORM, inReplyTo=msg.messageID, recipient=msg.sender, readonly=ro, index=ndx)
rmsg = ParameterRsp(performative=Performative.INFORM, inReplyTo=msg.messageID, recipient=msg.sender, readonly=ro, index=ndx)
length(rsp) > 0 && ((rmsg.param, rmsg.value) = popfirst!(rsp))
length(rsp) > 0 && (rmsg.values = Dict(rsp))
send(a, rmsg)
Expand Down
8 changes: 2 additions & 6 deletions src/coroutine_behavior.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,13 @@ end
function action(b::CoroutineBehavior)
b.control_task = current_task()
b.action_task = Task() do
try
logerror(b.agent) do
b.action(b.agent, b)
catch e
reporterror(b.agent, e)
end
b.done = true
yieldto(b.control_task)
end
try
logerror(b.agent) do
while !b.done
if !isnothing(b.block)
lock(() -> wait(b.block), b.block)
Expand All @@ -69,8 +67,6 @@ function action(b::CoroutineBehavior)
yieldto(b.action_task)
end
end
catch ex
reporterror(b.agent, ex)
end
b.done = true
b.control_task = nothing
Expand Down
4 changes: 2 additions & 2 deletions src/fsm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -214,15 +214,15 @@ function action(b::FSMBehavior)
_mutex_call(onenter, b.agent, b, b.state)
end
catch ex
reconnect(container(b.agent), ex) || reporterror(b.agent, ex)
reconnect(container(b.agent), ex) || logerror(ex, b.agent)
end
yield()
else
lock(() -> wait(b.block), b.block)
end
end
catch ex
reconnect(container(b.agent), ex) || reporterror(b.agent, ex)
reconnect(container(b.agent), ex) || logerror(ex, b.agent)
end
delete!(b.agent._behaviors, b)
b.agent = nothing
Expand Down
Loading
Loading