I once asked Joe Armstrong (RIP) about Erlang's famous hot reload feature compared to doing something like that, and he agreed with me that hot reload wasn't really worthwhile, as sexy as it might sound.
1. Don’t directly dlopen the .so file. Copy it to a temporary file and dlopen that. Much fewer headaches that way when the original .so file changes. Bonus points for using Linux’s memfd or FreeBSD’s SHM_ANON.
2. Rely as little on global/thread-local state as possible in the plug-in. Minimize the use of non-reentrant function calls in exported functions.
3. Don’t use musl. Its dlclose is a no-op and will cause memory leaks.
4. If the plug-in is to be accessed across multiple threads, copy the symbols you’ll need from the plug-in into a struct, and maintain a reference count alongside the struct. When reloading the plug-in, create a new struct for all new threads to use, and free the old struct when its reference count goes to zero.