Loaders and user hooks¶
HIX can dynamically load user code when the server starts.
Any .prg file located in www/loaders/ is compiled to .hrb, loaded into memory,
and its functions become globally accessible from any route,
middleware, controller, or view.
In addition to loading, HIX defines two lifecycle hooks:
| Hook | When it executes | Used for |
|---|---|---|
USERINIT |
After loading the loaders, before accepting traffic | Opening connections, caching data, etc. |
USEREXIT |
When stopping the server, before closing sockets | Closing connections, flushing buffers |
Both are optional: if they do not exist, HIX does nothing. If they do, they are invoked automatically.
When do you need it?¶
- To load application code without recompiling the HIX library.
- To initialize resources that your app needs to be available on the first request (DB pool, in-memory caches, index warmup, etc.).
- To release resources cleanly when the server stops (close handles, flush custom logs, close open sockets).
Directory www/loaders/¶
www/
└─ loaders/
├─ 00_bootstrap.prg ← definitions of USERINIT / USEREXIT
├─ helpers.prg ← utility functions
├─ tcustomers.prg ← models, classes, etc.
└─ …
Rules:
- Each
.prgis compiled only once to.hrb(which is saved in the same directory alongside the.prg). - On subsequent boots, HIX recompiles only those
.prgfiles whose mtime is later than the corresponding.hrb. Already up-to-date.hrbfiles are loaded directly. - Alphabetical order of the filename defines the load attempt order. If there are cross-dependencies between modules, HIX iterates through several passes until it resolves them all.
- Symbols are published with
hb_hrbLoad( 0x2, ... )(BIND_LAZY), so they become available to the rest of the server.
Each attempt (success or failure) is recorded in the Boot Log under the
"loaders" section:
[loaders]
OK file 00_bootstrap.prg
OK file tcustomers.prg
ERR file bad.prg -> Unterminated string 'x, HB_COMPILEFROMBUF
See Boot Log to inspect the results from code or expose them as JSON.
Complete lifecycle¶
This is the exact order of events when starting and stopping the server:
THixServer:Start()
│
├─ HIX_Loaders() ← compiles and loads www/loaders/*.prg
│
├─ Eval( ::bInit, SELF ) ← optional callback from the programmer (bInit)
│
├─ HIX_UserInit() ← invokes USERINIT() if hb_IsFunction("USERINIT")
│
└─ (opens port, accepts connections)
THixServer:Stop()
│
├─ HIX_UserExit() ← invokes USEREXIT() if hb_IsFunction("USEREXIT")
│
└─ (closes sockets, stops workers)
USERINIT runs before the server accepts the first request:
everything you prepare there will be available when traffic arrives.
USEREXIT runs before closing sockets, so you can still use the network if needed
(for example, to send a shutdown notification).
Hooks USERINIT / USEREXIT¶
How to declare them¶
They are declared as top-level FUNCTION (never STATIC) in any
.prg file in www/loaders/. They must be global so that hb_IsFunction()
resolves them.
// www/loaders/00_bootstrap.prg
FUNCTION USERINIT()
l( "Application initializing..." )
_OpenDbConnection()
_LoadCachesIntoMemory()
RETURN NIL
FUNCTION USEREXIT()
l( "Application shutting down..." )
_CloseDbConnection()
RETURN NIL
Critical rules¶
- They must be non-blocking. An infinite loop, a socket without timeout,
or a lock that doesn't release delays startup (in
USERINIT) or hangs shutdown (inUSEREXIT). - Exceptions are contained. HIX wraps both hooks in an internal
TRY/CATCH: ifUSERINITthrows, the error is traced and the server continues starting. The same applies toUSEREXIT. - They are reentrant. If for any reason you call
HIX_UserInit()multiple times,USERINIT()executes each time. There is no guard for "execute only once". USERINITruns on the server's main thread, before the accept loop. AnySTATICyou assign becomes accessible from workers through public accessors.
Example — DB connection pool¶
// www/loaders/00_bootstrap.prg
STATIC s_oDbPool := NIL
FUNCTION UserDbPool()
RETURN s_oDbPool
FUNCTION USERINIT()
LOCAL oErr
TRY
s_oDbPool := MyDbPool():New( "postgres://…", 8 )
s_oDbPool:Warmup()
l( "DB pool ready (8 connections)" )
CATCH oErr
le( "Could not initialize DB pool: " + oErr:description )
// do not rethrow - server starts anyway
END
RETURN NIL
FUNCTION USEREXIT()
IF s_oDbPool != NIL
s_oDbPool:CloseAll()
s_oDbPool := NIL
ENDIF
RETURN NIL
From any route:
oSrv:AddRouteGet( "users", "/users", {||
LOCAL oDb := UserDbPool()
USendJson( oDb:Query( "SELECT id, name FROM users" ) )
} )
Public API¶
HIX_Loaders() // compiles and loads www/loaders/*.prg. Returns .T. if all ok
HIX_GetLoaders() // array with the status of each loaded module
HIX_UserInit() // invokes USERINIT() if it exists. Internal TRY/CATCH
HIX_UserExit() // invokes USEREXIT() if it exists. Internal TRY/CATCH
HIX calls these functions automatically during Start() / Stop().
They are not usually called manually — but they are public in case you need
to do manual warm-up from a test or a CLI.
Structure of a module (HIX_GetLoaders())¶
Each entry in the returned array is a hash with these fields:
| Field | Type | Meaning |
|---|---|---|
file |
C |
Filename (e.g., tcustomers.hrb) |
loaded |
L |
.T. if hb_hrbLoad succeeded |
error |
L |
.T. if compilation or load failed |
msg |
C |
Error description if error == .T. |
oError |
O |
Harbour error object (or NIL) |
oHrb |
C |
Binary contents of the .hrb |
pSym |
P |
Symbolic pointer to the loaded module |
process |
L |
.T. if the .prg was (re)compiled now |
Diagnosis¶
If something is not loading as expected:
- Check the Boot Log:
HIX_BootLogShow()to console, orHIX_BootLogSection( "loaders" )from code. Each.prgappears with OK/ERR and, in case of error, the exact description. - Verify the hook is published:
hb_IsFunction( "USERINIT" )should return.T.. If it returns.F., check that the function is declared as a top-levelFUNCTION(notSTATIC) in some.prgfile inwww/loaders/. - Errors in
USERINITdo not crash the server: even though the server continues starting, the exception is recorded in the trace (_t()). See Tracing to read it. - Regenerate
.hrb: if you suspect the cached.hrbis corrupted, delete it — HIX will recompile it from the.prgon the next boot.