As I'm developing my own RPG AltisLife alike mission I also developing my own backend extension instead of rely on existing ones like extDB3. As I looked up a few C/C++ tutorials about how to develop dynamic libraries (both Windows-DLL as well as Linux-SO) I learned they also have constructors and destructors, both supposed to be called from the framework responsible for loading and unloading them. As instructed by several I asked and looking up many tutorials and even existing ArmA3 extensions I designed my extension to not only provide the ArmA3 extension api specific functions but also providing a constructor and destructor.
So, as I'm not just new to ArmA3 development but also to C/C++, but have about 15 years experience in Java, I tested my fresh clean new extension with some simple stuff. And by the console logs I noticed: the constructor gets called correctly, as well as the extension api entry and the extension methods. But, what does not get any call is the library destructor.
In addition, no matter how I do try to shut down the server, it doesn't seem to correctly shut down - but seem to just "crash to coredumb" - but I guess that's for another report ...
Description
Details
- Severity
- Minor
- Resolution
- Unable To Duplicate
- Reproducibility
- Always
- Operating System
- Linux x64
- Operating System Version
- opensuse leap 15.2
- Category
- Dedicated Server
That's my very basic extension code just with some debug output:
#include <cstring> #include <iostream> extern "C" { void RVExtension(char *output, int outputSize, const char *function); int RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc); void RVExtensionVersion(char *output, int outputSize); void RVExtensionRegisterCallback(int(*callbackProc)(char const *name, char const *function, char const *data)); } int(*callbackPtr)(char const *name, char const *function, char const *data) = nullptr; void __attribute__((constructor)) setup(); void __attribute__((destructor)) teardown(); void RVExtension(char *output, int outputSize, const char *function) { std::cout << "RVExtension: " << function << std::endl; std::strncpy(output, "extmuuh v3", outputSize - 1); } int RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc) { std::cout << "RVExtensionArgs: " << argc << " : "; for(int i=0; i<argc; i++) { std::cout << argv[i]; if(i!=(argc-1)) { std::cout << " - "; } } std::cout << std::endl; std::strncpy(output, "extmuuh v3", outputSize - 1); return 0; } void RVExtensionVersion(char *output, int outputSize) { std::cout << "RVExtensionVersion" << std::endl; std::strncpy(output, "extmuuh v3", outputSize - 1); } void RVExtensionRegisterCallback(int(*callbackProc)(char const *name, char const *function, char const *data)) { std::cout << "RVExtensionRegisterCallback" << std::endl; callbackPtr = callbackProc; } void setup() { std::cout << "extmuuh setup" << std::endl; } void teardown() { std::cout << "extmuuh teardown" << std::endl; }
As I'm using the 64bit version (can't get the 32bit to work on my opensuse leap 15.2 as there's no 32bit of TBB provided anymore) I use this line to build a SO:
g++ -shared -fPIC -o extmuuh_x64.so extmuuh.cpp
I also have these SQF for auto-init my extension (I just copied it from extDB3):
class CfgPatches { class extmuuh { name = "extmuuh"; projectName = "extmuuh"; author = "muuh"; url = "https://cryptearth.de"; version = 1.0; requiredVersion = 2.00; requiredAddons[] = {}; units[] = {}; weapons[] = {}; }; }; class CfgFunctions { class extmuuh { class system { file = "extmuuh\system"; class preInit { preInit = 1; }; }; }; };
diag_log "-----extmuuh-----"; private _result = "extmuuh" callExtension "extmuuh"; if(_result == "") then { diag_log "extmuuh failed"; } else { diag_log "extmuuh success"; }; diag_log "-----extmuuh-----";
Simply starting the server with my extension packed as pbo and the SO named correctly in the correct folder - and it works right away.
Shutting down the server, no matter if CTRL+C on console, #shutdown command when logged in as admin, or even using it via RCon - my extension never shows the teardown message to indicated the destructor got called - hence I assume there's no correct extension unload at server shutdown.
nothin required
Event Timeline
Thank you for your reply.
As one can see, my code does contain a destructor. I also had it tested by some C/C++ devs that it gets called correctly when dlclose() is called. So they suspect that the server shutdown just doesn't call dlclose() on loaded extensions.
I was also able to find another source which mentioned, that dlclose(), according to POSIX standards, is only required to invalidate the given handle. So it's up to runtime if a call to dlclose() actually calls an maybe existing destructor. This source also mentions some difference between using gcc vs clang - but I haven't tried using clang yet, as I just read about it after your reply.
So, the question to the devs is: Is dlclose() called on loaded extensions during shutdown and if it's maybe my way of creating my extension or even my runtime I execute it on to cause my issue, or if the shutdown just doesn't call dlclose() at all.
Anyway, I'll give it a try to use clang and see if this makes any difference. I'll report back after testing.
Ok, we gonna put our best man on it but dont expect immediate fix, we have a lot of other work
Thank you once again for having at least a look at it.
As promised, I tried to use clang - but, unfortunately, result is the same.
I already came up with a workaround: As the SQF command "serverCommand" allows to execute commands from within SQF I may be able to get my cleanup working by starting from my backend and using the callback pointer to execute a small SQF as the last step which then will call the shutdown command. This way I don't really have to worry about my extensions cleanup done in the destructor as I can have it already done when calling the final shutdown script.
I also tested this issue on several other environments:
- arch
- debian
- fedora
- gentoo (at least I tried - but compiling the kernel from scratch just took too much effort and time)
- slackware (from which opensuse descends)
All environments showed the very same issue. As I reported this to the ones I asked for testing my short code they agreed upon that if an issue is exactly the same on so many so different styles of linux mainline distros it's most likely the application causing the issue, although without having a look into source noone is able to confirm or deny on that.
I'm sorry I didn't tested on windows or even try some stunt with the DotNET framework - but as my extension is only meant as a small connector to a java backend using some plain sockets for brdiging the gap between SQF and my backend, and it's meant to run on a linux server, and as just setting up the required dev environment to build a small DLL - I just didn't put in the effort. Also, as you mentioned: it does work on windows.
Anyway - I'm surprised that either noone noticed this issue yet since for how long arma 3 linux dedicated server + extension support is out - or noone cared. Also, as said earlier: I might go the other around instead of try to rely on the lib destructor. Issue some final shutdown command and then cleanly disconnect the socket should be rather easy to implement.
Which linux server have you tried, x64? It has been out for a short time. Would actually help if you try 32 and confirm if it is also broken on it
Although I didn't repeat it for all the tested systems from a short test on my main server it seems the issue is the same for the regular x86 binary.
Arma server killed via signal or #shutdown exits via exit(0) which will unload shared libraries due to their refcount going to zero, but won't call destructors (unlike windows which will call on unload).
We need to manually call dlclose on linux.
Easy fix, I can probably get it done till 2.04 but for the next couple week theres other stuff that takes priority.
You'll see it on performance/profiling branch first.
Anyway - I'm surprised that either noone noticed this issue yet since for how long arma 3 linux dedicated server + extension support is out - or noone cared.
Yeah surprised me too, I guess noone cared.
Many thanks for that reply with some inside information on what actually happens when #shutdown command is called.
I really much appreciate you even take time to look into it although it doesn't seem to be something caused issues in the past. So I fully understand it to be some very low priority, and maybe it only gets fixed "along the way while fixing other issues".
We need to manually call dlclose on linux.
So I finally got the time to check this. We already do that, we call dlclose on #shutdown via the destructor of a singleton that keeps the extensions.
I just stepped through in gdb,dlclose is called, extension properly unloads.
And your logging from the example is also printed
17:15:58 Mission id: b276e1f8b58393bbe11c0d14af8bc90a35aa7670 17:15:58 Game started. extmuuh setup 17:16:04 CallExtension loaded: asm (/home/steam/arma3/@asm/asm_x64.so) [Test-Extension v1.0] 17:16:13 Admin logged in, player: dedmen 17:16:17 Deinitialized shape [Class: "C_Soldier_VR_F"; Shape: "a3\characters_f_bootcamp\common\vr_soldier_f.p3d";] 17:16:17 Deinitialized shape [Class: "B_soldier_AAA_F"; Shape: "a3\characters_f\blufor\b_soldier_01.p3d";] 17:16:17 Deinitialized shape [Class: "B_soldier_AR_F"; Shape: "a3\characters_f\blufor\b_soldier_02.p3d";] 17:16:17 ../lib/Network/networkServer.cpp ClearNetServer:NOT IMPLEMENTED - briefing! Class WeaponFireGun destroyed with lock count 3 Class Table destroyed with lock count 3 17:16:18 Extensions: 17:16:18 asm (/home/steam/arma3/@asm/asm_x64.so) [Test-Extension v1.0] extmuuh teardown [Inferior 1 (process 3517) exited normally] (gdb)