Hi, I'm new to Nim, and found the language nice, and coming from Python, I'm trying to port some apps, but I'm hitting lots of road blocks. This time, with closures.
import os
from posix import onSignal, SIGINT
type
App = object
proc cleanup(app: App) =
discard
proc start*(app: var App) =
onSignal(SIGINT):
echo "Exiting: ", sig
app.cleanup
while true:
os.sleep(10000)
var app = App()
app.start
The compiler complains that 'app' is of type <var App> which cannot be captured as it would violate memory safety. I tried with system.closureScope, but it fails as well with type mismatch: got <cint, proc (signal`gensym349052: cint){.closure, gcsafe, locks: 0.}>. No idea why it doesn't match required type for handler: proc (a: cint){.noconv.}.
So my question is, how do I capture the environment with onSignal?
I looked at https://forum.nim-lang.org/t/5564#34689, but I'm not sure if this will solve my case. Any insights?
Try this:
import os
from posix import onSignal, SIGINT
type
App = object
template cleanup(app: App) =
discard
proc start*(app: App) =
onSignal(SIGINT):
echo "Exiting: ", sig
app.cleanup
while true:
os.sleep(10000)
var app = App()
Thanks, but removing the var is not an option, since the cleanup will change some variables. In this example, I used the simplest signal (Ctrl-C), but in reality, this will also be used for SIGHUP, to reload the settings, etc., thus it'll change the state of the app.
Changing the parameter of start to var, introduces the same problem, even with cleanup being a template.
So, is there another way to handle SIGHUP, while changing the app state? Might I be approaching this the wrong way?
Thanks.
type
App = ref object
should also work
The first solution implies the instance is already available when using onSignal, which is not the case, since this is a library, which will be used by others. They will probably inherit this App (which I forgot to add of RootObj), so it must be run when the user creates the instance and start the app, not when they import this module.
The second solution seems like it would have problems if the user (the one who imports this library), does not understand that templates might have adverse effects, if used with functions that return an App, but also changes the app, like this:
template start*(app: var App): bool =
app.running = true
app.anotherVar = 1
... onSignal, etc.
proc init*(app: App): var App =
app.ii += 2
var myapp = App()
myapp.init.start
From my understanding, app.ii would be 4, since it will run myapp.init 2 times from the template (one on line app.running = true, and another from app.anotherVar = 1, which it replaces with app.init.anotherVar = 1, correct? I'm reluctant to expose this to the users, since it requires under the hood knowledge of how it is implemented, as opposed to just know it is implemented.
Let me attempt to explain. First, look at the implementation of onSignal:
https://github.com/nim-lang/Nim/blob/version-1-0/lib/posix/posix.nim#L1038
it is a very thin wrapper over the POSIX signal handler callback function.
http://man7.org/linux/man-pages/man2/signal.2.html
This is nothing like how Python does it!
https://docs.python.org/3.4/library/signal.html#execution-of-python-signal-handlers
A Python signal handler does not get executed inside the low-level (C) signal handler. Instead, the low-level signal handler sets a flag which tells the virtual machine to execute the corresponding Python signal handler at a later point(for example at the next bytecode instruction).
Python has a global stub sighandler function that punts back to the python VM and dispatches the right thing. This is why you can do things like use Closures / access outside variables from Python.
You have to think more like a C programmer when dealing with low level POSIX stuff in Nim. This means no closures, unfortunately.
If you need to access external variables, you must use a global variable, as @enthus1ast shows in their example.
You have to consider that the POSIX only allows a single global signal handler for a process.
You could emulate something like what Python does by creating a global dispatcher sighandler proc that internally loops through and destroys all active app objects.
I personally consider this bad practice though. In general, I never want my library code to take over my application signal handler. It's my job as the application developer to handle signals. If I want to set up my own signal handler, how do I even know that you have already set one? I could easily end up just overwriting your signal handler with my application specific one. Especially when dealing with low level / C code, you rarely see library code do signal handling for this reason.
Thanks, this answer clarifies a lot. Unfortunately, since my library is more like a framework, with users that probably don't understand the concept of signals, etc., we felt the need to handle the cleanup for them, especially when it comes to closing handshakes and the like.
Since this is an internal project, I believe we can guide our users to not handle signals by themselves. I'm not too fond of global variables as well, but as long as it's just my library handling them, then I can deal with it.
Thanks.
Just to summarize here:
It is not possible to use closures, thus not possible to access the environment outside of global variables. And since there can be only one handler (per signal or per process?), it is not possible to "add" a proc to run as a signal comes. It will always overwrite the previous handler (if any).
You can always use poor man's closures: add a field that takes a tuple, for proc + environment for example
type Foo = object
data: int
handler: tuple[onSignal: proc(env: pointer) {.nimcall.}, env: pointer]
You can have an array of handlers or define multiple handlers in the proc.Thanks @Araq, addQuitProc is one the procs I was looking for. I don't thinks I'll hit limit of 30 procs, so It'll probably suffice. Is there anything more generic that I can use for SIGHUP, to reload?
I'm implementing a queue of procs to call when SIGHUP is received, but adding an obj to a global seq from an inherited instance raises ObjectAssignmentError in lib/system/assign.nim(97) genericAssignAux. I can add a ptr obj, but when I cast it back, it "loses" the inheritance, and doesn't call the right method.
A minimal example:
type
App* = object of RootObj
running*: bool
PType = proc(app: App): bool {.nimcall.}
PTuple = tuple[app: App, f: PType]
BApp = object of App
var s = newSeq[PTuple]()
method reload*(app: App): bool {.base.} =
echo "App reloaded"
true
proc start*(app: App) =
s.add((app, reload))
echo repr(s)
proc stop*(app: App) =
for e in s:
discard e.f(cast[App](e.app))
method reload*(app: var App): bool =
echo "BApp reloaded"
return true
when isMainModule:
var app = BApp()
app.start
app.stop