https://downloads.haskell.org/~ghc/latest/docs/html/libraries/unix-2.7.2.2/System-Posix-Process.html#v:getGroupProcessStatus (at a lower level, this is waitpid() with a negative process ID, expressing the process group whose leader has abs(process ID)).
And yes, if a process puts itself into its own process group, it will be immune to control in this fashion. You can do a bit more with sessions instead of pgroups, but then a process could put itself into its own session. cgroups are pretty much the only way to avoid this… if and only if you prevent processes in a given cgroup from creating new cgroups. It's something of an infinite regression.
As for cleanup, the convention is you use signal 15 (SIGTERM) to indicate "clean up and exit". If after a reasonable amount of time (typically some number of seconds) something is still running, you repeat with signal 9 (SIGKILL, which can't be blocked or masked). SIGTERM is usually sent to the entire process group, as individual processes may have their own distinct cleanup needs.