Variable is of type $Box inside a closure
Acton Version
0.26.0.20251024.17.58.51
Steps to Reproduce and Observed Behavior
I have tried to come up with the "minimum reproduction" on sorespo that shows the problem: https://github.com/orchestron-orchestrator/sorespo/tree/box-type
To reproduce, open 2 terminals in sorespo/test/quicklab-notconf:
- In terminal 1 run
make start copy run - In terminal 2 run
make send-config-async FILE=netinfra.xml
Output is as follows:
curl -X PUT -H "Content-Type: application/yang-data+xml" -H "Async: true" -d @netinfra.xml http://localhost:62911/restconf
{"status":"ok","var1":"Bloop","type_of_var1":"str","var2":"Bloop","type_of_var2":"$Box"}
Assignig a value to var2 within a try: block seems to hit an unboxing bug. The type of var2 is a string as expected when checked right after assignment, but not inside config_done().
Expected Behavior
var1 and var2 should both be string types.
Here is a minimal reproduction:
actor Foo():
def f(cb):
cb()
actor main(env: Env):
f1 = Foo()
def do_stuff():
var1 = None
var2 = None
def my_callback():
print(type(var1))
print(type(var2))
print(var1)
print(var2)
var1 = "hello"
try:
var2 = "world"
except:
return # it is return here that turns var2 into $Box instead of str
#pass # using pass is fine...
f1.f(my_callback)
do_stuff()
env.exit(0)
which prints:
str
$Box
hello
world
see the inline comment - changing return to pass makes this go away.
@sydow the question goes to you, since we see $Box (and I don't think we should ever be able to observe that using type()!?), but maybe the actual problem is elsewhere??
Getting the $Box type affects behavior. We pass these values on and ultimately end up in json.encode() which throws an error:
Unhandled exception in actor: sorespo.main[-12]:
ValueError: jsonQ_encode_dict: for key var2 unknown type: $Box
However, the workaround is trivial, we just explicitly str(var2), so what remains can be considered a cosmetic bug, thus not critical for now...
Well, I don’t know… I have no idea at all what the purpose of type $Box is, and I am just barely aware of the existence of, but have never used, the function “type" (which one seems to be discouraged from using in the comment in builtin.act). I tried to locate the implementation of this function in the compiler sources, but failed so far.
I compiled the minimal example and can see that after deactorization var2 has type ?str, but after CPS conversion it has type $Box[?str]. Glancing at the code in CPS.hs I get the impression that this has to do with volatility.
If the issue has to do with unboxing I accept that I should be responsible, but I don’t understand how it is related. I also note the remark in the issue that the type $Box should not be observable through the use of the function type. But var2 has type $Box and the function is supposed to return the type of it as a string??? This makes no sense to me, which indicates that I am not the right person to fix this.
@sydow ah ok, maybe it was too sloppy of me - sorry about that. I assumed $Box was related to (un)boxing but yeah this $Box seems not, guess it goes to @nordlander :) @nordlander note how this is not time critical, so don't let it distract you right now
Blush, this is entirely my fault! I introduced the $Box type as a way of handling certain reassignment issues in the CPS pass; essentially by replacing the reassignments with mutation of a ”box” object instead (whose address becomes a read-only value that never gets reassigned).
But this had completely slipped my mind as I started to speculate about unboxing bugs. Embarrassing!
I’ll take a deeper look into this in due time. But one early suspicion (whatever that’s worth!) is tha it’s just the introspective type feature that needs a fix. Basically, whever a $Box value is encountered, it’s really its payload that’s of interest.
Additional note: None-checking on this $Box type does not work, even if it was initialized as None and never set afterwards var2 is not None evaluates as True (My app actually cashes when it tries to read this var2 inside of the None checked block).
That too is easily worked around by initializing it as a string:
def _on_http_server_request(server, request, respond):
tmf_id = "" # FIXME: We cannot None check $Box type vars, workaround for acton#2494
def config_done(result):
if isinstance(result, Exception):
respond(400, {}, json.encode({"status": "error", "message": str(result)}))
else:
tmf_id_str = str(tmf_id)
if tmf_id_str != "":
respond(200, {}, json.encode({
"status": "ok",
"id": tmf_id_str
}))
else:
respond(200, {}, json.encode({"status": "ok"}))
So @nordlander as @plajjan said, it is low priority, I have a perfectly acceptable workaround available.