I'm trying to create a process from my program using CreateNewProcTags().
It works just fine until I exit the main program before exiting the process. I get a grimreaper inside DOS.library. Exiting the process before the program and it all works just fine.
You could "transfer" the seglist ownership to the process (use NP_SegList tag with CreateNewProc), but you still have to prevent the program from freeing it itself.
In order for a child process to be completely separate from its parent it needs to have it's own seglist.
This means using LoadSeg(), NP_SegList instead of NP_Entry and setting NP_FreeSegList to TRUE.
TBH I would suggest using SystemTags() with SYS_Asynch enabled instead of CreateNewProc(), since CreateNewProc() generally doesn't work so well with C startup code. For an example on how to do this you can look at the splashlauncher source code from OS4Depot.
Centaurz's suggestion won't work since there is no reference counter inside seglist so you can't make the OS free the seglist only after the last process using it has ended (this could be either parent or child).
Rigo explained to me that it's probably due to using newlib. The newlib base is deallcoated by the parent. The only way to avoid it is to use NP_Child, TRUE which isn't possible with the type of application I'm making. Seems I have to use clib2.
As for SystemTags, I'm not really interested in calling an external program.
The subprocess is still running in the code space loaded for the parent. The parent exits, unloads the code, subprocess crashes. That's pretty well outlines your problem.
Two basic ways to fix this....
1) Use NP_Child tag and make the parent Wait()/WaitMsg() until the child exits, see; NP_NotifyOnDeathMessage and NP_NotifyOnDeathSigTask. Set up a message/signal and only allow the parent to leave only when the child notifies you of its death. However, this means that the parent must go to sleep until the child exits, which means that if the parent was a shell process, the CLI will be "dead" until the child ends.
2) If you wish to affect a complete detachment of the child and allow the parent to completely exit while allowing the child to continue execution in the old code space, then the resources that are shared by the child MUST also be detached.
This can be a little more complicated and not really for the beginner. Nevertheless, here's an outline of what needs to be done.
The easiest way is to call CreateNewProc() with no tags for the NP_Input,NP_Output,NP_Error, these will then open a default "NIL:" stream for the child (and 0 for the error stream). (and with NP_Child,FALSE)
With that taken care of, (here's the tricky part), because as the seglist is being shared, you must make sure it is not freed by the parent process on exit,
You can transfer ownership to the child process, so that it frees it on exit instead, so it is not done by the parent. To do this, you need to determine who loaded the original, this can be the shell or 'other'. If the shell loaded it, you need to stop it from unloading the command on exit.
Here's some pseudocode.....
============
BPTR seg = ZERO; struct CommandLineInterface *cli = IDOS->Cli(); if( cli ) { seg = cli->cli_Module; cli->cli_Module = ZERO; /* stop shell freeing the seg */ } else /* not a cli, try 'other' load methods */ { seg = IDOS->GetProcSegList(NULL,GPSLF_SEG); if( seg ) { struct Process *me = (struct Process *)FindTask(0); me->pr_Flags &= (~PRF_FREESEGLIST); } /* clear the flag for parent to free seglist */ }
if( seg ) /* did we find a loadsegged seglist ? */ { IDOS->CreateNewProcTags( NP_Entry, child_entry_func, /* entry() here */ NP_SegList, seg, /* free this seg on exit */ NP_FreeSegList,TRUE, /* make it so */ ... TAG_END);
NOTE WELL: You cannot call newlib functions in the child process unless you re-open the library within the child process itself, and set the childProc->pr_CLibData to the newlib base as there will be no default library context available to the child.
Better to just use the IDOS-> functions while inside the child process.
Also note that as the child is now responsible for freeing the seglist on exit, you MUST make sure the parent now exits BEFORE the child does, otherwise you will find yourself in the same situation.
Unfortunately I need to be able to create an arbitrary amount of processes with no prior knowledge as to which process termination that should deallocate the seglist.
It would have been better for the OS to keep track of each seglist subscriber using a reference counter and not actually deallocate it until there's no subscriber left (As salass00 suggested). I'm surprised that the OS doesn't already do this when it permits creating processes (NP_Entry+NP_Child,FALSE) that shares the seglist.
Seeing you can see the problem area ahead of you, then make it so EVERY child can deallocate the seglist, by creating the children with the same seglist tag, but set the NP_FreeSegList,FALSE on child creation.
Then, use ProcessScan() at the exit point of the common child code to find out how many of your child processes are still running, and when the last one is exiting (ie; only 1 child) then delegate that child to be the process that frees the seglist by setting its proc->pr_Flags |= PRF_FREESEGLIST; bit, then simply return() to DOS.
There is example code in the ProcessScan() autodoc. See the "SignalMyChildProcsToExit" example code and just lose the Signal(), and use any of the child procs pr_ParentID to scan for other descendants, it will return the child process count.
Just beware of handlers being started up, therefore use a second tier check, i'd suggest a magic cookie in NP_UserData, or NP_EntryData or NP_ExitData or such, when starting the actual child processes.