Happy April Fool's Day to all my readers, and welcome to one web site which won't irritate you all day with bizarre practical jokes. Instead, I bring you another edition of Friday Q&A. In this edition, I will discuss various ways of handling signals in Mac programs, a topic suggested by friend of the blogLandon Fuller.
Signals
Signals are one of the most primitive forms of interprocess communication imaginable. A signal is just a small integer sent to a process. You can send a signal using thekill command, which also has a corresponding function available from C.
When a signal is delivered, it can terminate the process, pause/resume the process, be ignored, or invoke some custom code. That last option is called signal handling, and that is what I want to discuss today.
The list of defined signals can be seen in the headersys/signal.h. Many of these are used for familiar purposes.SIGINT is the signal generated when you press control-C in the shell.SIGABRT is used to kill your program when you callabort(), andSIGSEGV is the infamous segmentation fault, which pops up when you dereference a bad pointer.
Signal handling is esoteric and most programs don't need to worry about it at all. However, there are cases where it can be useful. For terminal and server programs, it's handy to catchSIGHUP,SIGINT, and other similar signals to do cleanup before exiting, as a sort of low-level version of Cocoa'sapplicationWillTerminate:. TheSIGWINCH signal is handy for sophisticated terminal applications.SIGUSR1 andSIGUSR2 are user-defined signals which you can use for your own purposes.
sigaction
The lowest level interface for signal handling is thesigaction function. It provides some sophisticated and arcane options, but the important part is that it allows you to specify a function which is called when the signal in question is delivered:
staticvoidHandler(intsignal){// signal came in!}structsigactionaction={0};action.sa_handler=Handler;sigaction(SIGUSR1,&action,NULL);
Wrong.
Reentrancy
The problem is that signals are delivered asynchronously, and the function registered here is also invoked asynchronously. Code always has to run on a thread somewhere. Depending on how the signal is generated, the handler is either run on the thread that the signal is associated with (for example, aSIGSEGV handler will run on the thread that segfaulted) or it will run on an arbitrary thread in the process. The problem is that it's essentially an interrupt in userland, and whatever code was running when it came in will be paused until the handler is done.
As anyone who was around in the classic Mac days knows, writing code that runs in an interrupt is hard. The problem isreentrancy. Many people confuse reentrancy with thread safety, but they are not the same concept, although they are somewhat similar.
Thread safety means that a particular piece of code can run on multiple threads at the same time safely. Thread safety is most commonly accomplished by using locks. A call acquires a lock, does work, releases the lock. A second thread that comes along in the middle will block until the first thread is done.
If code isreentrant that means that a particular piece of code can run multiple timeson the same thread safely. This is different and considerably harder.
What if you take the thread safety approach of locking and apply it to reentrancy? The first call acquires the lock. While it's active, the code is called again. It tries to acquire the lock, but the lock is already taken, so it blocks. However, the first call can't run until the second call is done. The second call can't run until the first call is done. The result is a frozen program.
Writing reentrant code ishard, and as a result very few system functions are reentrant. Because a signal handler functions as an interrupt, it can only call reentrant code. You can't call something as simple asprintf safely, becauseprintf could take a lock, and if there's already an active call toprintf on the thread where the handler runs, you'll deadlock.
Thesigaction man page gives a list of functions you are allowed to call from a signal handler. It's pretty limited.
The complete list is:_exit(),access(),alarm(),cfgetispeed(),cfgetospeed(),cfsetispeed(),cfsetospeed(),chdir(),chmod(),chown(),close(),creat(),dup(),dup2(),execle(),execve(),fcntl(),fork(),fpathconf(),fstat(),fsync(),getegid(),geteuid(),getgid(),getgroups(),getpgrp(),getpid(),getppid(),getuid(),kill(),link(),lseek(),mkdir(),mkfifo(),open(),pathconf(),pause(),pipe(),raise(),read(),rename(),rmdir(),setgid(),setpgid(),setsid(),setuid(),sigaction(),sigaddset(),sigdelset(),sigemptyset(),sigfillset(),sigismember(),signal(),sigpending(),sigprocmask(),sigsuspend(),sleep(),stat(),sysconf(),tcdrain(),tcflow(),tcflush(),tcgetattr(),tcgetpgrp(),tcsendbreak(),tcsetattr(),tcsetpgrp(),time(),times(),umask(),uname(),unlink(),utime(),wait(),waitpid(),write(),aio_error(),sigpause(),aio_return(),aio_suspend(),sem_post(),sigset(),strcpy(),strcat(),strncpy(),strncat(),strlcpy(),strlcat().
Finally, the list ends with this amusing note: "...and perhaps some others." "Perhaps" is not a nice word to run into in this sort of documentation.
You can call your own reentrant code, but you probably don't have any, because it's hard to write, it can't call any system functions except from the above list, and you never had any reason to write it before. For the Objective-C types, note thatobjc_msgSend is not reentrant, so you cannot use any Objective-C from a signal handler.
There is very little that you can do safely. There is so little that I'm not even going to discuss how to get anything done, because it's so impractical to do so, and instead will simply tell you to avoid using signal handlers unless you really know what you're doing and you enjoy pain.
Fortunately, there are better ways to do these things.
kqueue
One of those better ways is to usekqueue. This is a low level operating service which allows a program to monitor many different events, and one of the events it can monitor is signals. You can create akqueue just for signal handling, or you can add a signal handling event to an existingkqueue you already have within your program.
Setting things up is a bit more involved, but all in all not too hard. First, thekqueue is created:
intfd=kqueue();
structkeventevent={SIGUSR1,EVFILT_SIGNAL,EV_ADD,0,0};kevent(fd,&event,1,NULL,0,NULL);
kqueue to watch forSIGUSR1 being delivered to the process. Note thatkqueue exists separately from the lower levelsigaction handling. Because we don't want the program to terminate when the signal is delivered, which is the default behavior, we also have to tellsigaction to ignore it:structsigactionaction={0};action.sa_handler=SIG_IGN;sigaction(SIGUSR1,&action,NULL);
kqueue is now ready. We can wait for it to receive an event by callingkevent again, this time not adding anything, but having it give us an event:structkeventevent;intcount=kevent(fd,NULL,0,&event,1,NULL);if(count==1){if(event.filter==EVFILT_SIGNAL)printf("got signal %d\n",(int)event.ident);}
printf or any other code when handling the signal. Convenient!kqueue isn't always all that convenient to use in real programs, though. There are two reasonable ways to do it. One way is to have a dedicated signal handling thread which sits in a loop callingkevent repeatedly. Another way is to add thekqueue file descriptor to your runloop using something likeCFFileDescriptor to integrate it with your Cocoa runloop. However neither of these is particularly great.
GCD
Finally we reach a signal handling solution which is extremely easy to use: Grand Central Dispatch. In addition to the better-known multiprocessing capabilities, GCD also includes a full suite of event monitoring abilities which match those ofkqueue. (And in fact, GCD implements them usingkqueue internally.)
To handle a signal with GCD, we create a dispatch source to monitor the signal:
dispatch_source_tsource=dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL,SIGUSR1,0,dispatch_get_global_queue(0,0));
dispatch_source_set_event_handler(source,^{printf("got SIGUSR1\n");});dispatch_resume(source);
kqueue, this exists separately fromsigaction, so we have to tellsigaction to ignore the signal:structsigactionaction={0};action.sa_handler=SIG_IGN;sigaction(SIGUSR1,&action,NULL);
That's it! Every time aSIGUSR1 comes in, the handler is called. Because the source targets a global queue, the handler automatically runs in a background thread without interfering with anything else. If you prefer, you can give GCD a custom queue, or even the main queue, to control where the handler runs. Like withkqueue, because the handler runs normally on a normal thread, it's safe to do anything in it that you would do in any other piece of code. GCD makes signal handling convenient, easy, and safe.
Conclusion
Signal handling is a rare requirement, but sometimes useful. Using the low levelsigaction to handle signals makes life unbelievably hard, as the signal handler is called in such a way as to place extreme restrictions on the code it contains. This makes it almost impossible to do anything useful in such a signal handler.
The best way to handle a signal in almost every case is to use GCD. Signal handling with GCD is easy and safe. On the rare occasions where you need to handle signals, GCD lets you do it with just a few lines of code.
If you can't or don't want to use GCD but still want to avoidsigaction,kqueue provides a good middle ground. While it's more complicated to set up and manage than the GCD approach, it still works well to handle signals in a reasonable manner.
That wraps up today's April Fool's edition of Friday Q&A. Come back in two weeks for the next one. Until then, as always, keep sending me your ideas for topics. Friday Q&A is driven by reader suggestions, so if you have something you would like to see covered,send it in!
write() is a safe call to make from one. Create a pipe, stick the read end into your event system, and write a byte to the write end to signal. Make sure the pipe is nonblocking, though, otherwise you could be in serious trouble.
#define LEAKS_LOG "~/Library/Logs/DiagnosticReports/JMP-LeaksReport.txt"
void hostDebugLeaks()
{
/*
* This function is called from JSL via the
* Debug( Leaks );
* command.
*
* It works best if you have launched JMP with the symbol MallocStackLogging set to 1,
* like so:
* MallocStackLogging=1 ./JMP.app/Contents/MacOS/JMP
*
* If leaks are found, this function writes a leaks report to ~/Library/Logs/
* DiagnosticReports/JMP-leaks.log. JMP is then exited.
*
* This can be used with the TestBot in leaks detection mode. In this mode, TestBot will
* launch JMP with MallocStackLogging enabled, then run the UT Framework with
* _utDailyBuild=1 and _utLeaks=1. This causes UT Framework to check for leaks after
* running each unit test. If a leak is found, JMP exits and TestBot captures the leaks
* report for the leaking test. Then JMP is restarted and the test stream resumes on the
* following test.
*/
#if 0
// Debugging - force a leak
int * leaky = new int[42];
leaky = 0;
#endif
// Run the 'leaks' command on our process; capture output; look for magic "no leaks" string
JString leaksCmd( "leaks ^PID 2>&1 | tee ^LOG | grep \": 0 leaks for 0 total leaked bytes.\"" );
leaksCmd.replace( "^PID", getpid()).replace( "^LOG", LEAKS_LOG );
int status = system( leaksCmd.value());
// If status is 0, it means the magic "no leaks" string was found, so we don't have any leaks.
// Delete the log file and return.
if( status == 0 ) {
JString rmLogCmd( "rm -f ^LOG" );
rmLogCmd.replace( "^LOG", LEAKS_LOG );
system( rmLogCmd.value());
return;
}
// We have a leak; leave the log file intact and force JMP to exit
NSLog( @"Leaks detected; exiting" );
exit( EXIT_FAILURE );
}
sub process_leaks_report {
my( $file ) = @_;
return unless -e $file;
my $modified = 0;
my $contents = '';
open( LOG_FILE_IN, '<', $file ) || die "Cannot open $file: $!";
while( <LOG_FILE_IN> ) {
chomp;
if( s/^(\s*Call stack: .*?:) \| // ) {
$contents .= "$1\n\t\t";
$contents .= join( "\n\t\t", reverse split( / \| /, $_ ));
$contents .= "\n";
++$modified;
next;
}
$contents .= "$_\n";
}
close( LOG_FILE_IN );
if( $modified ) {
open( LOG_FILE_OUT, '>', $file ) || die "Cannot create $file: $!";
print LOG_FILE_OUT $contents;
close( LOG_FILE_OUT );
}
}
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"tel://1111111"]];, is that at all possible?Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.