Optimizing Applications For NSPR

NetScape Portable Runtime (NSPR) tries to provide a consistent level of service across the platforms it supports. This has proven to be quite challenging, a challenge that was met to a large degree, but there is always room for improvement. The casual client may not encounter a need to know the details of the shortcomings to the level described here, but if and when clients become more sophisticated, these issues will certainly surface.

This memo is by no way complete.

Multiplatform

  • Do not call any blocking system call from a local thread. The only exception to this rule is the <tt>select()</tt> and <tt>poll()</tt> system calls on Unix, both of which NSPR has overridden to make sure they are aware of the NSPR local threads.
  • In the combined (MxN) model, which includes NT, IRIX (sprocs), and pthreads-user, the primordial thread is always a local thread. Therefore, if you call a blocking system call from the primordial thread, it is going to block more than just the primordial thread and the system may not function correctly. On NT, this problem is especially obvious because the idle thread, which is in charge of driving the asynch io completion port, is also blocked. Do not call blocking system calls from the primordial thread. Create a global thread and call the system call in that thread, and have the primordial thread join that thread.
  • NSPR uses timer signals to implement thread preemption for local threads on some platforms. If all the software linked into the application is not ported to the NSPR API, the application may fail because of threads being preempted during critical sections. To disable thread preemption call <tt>PR_DisableClockInterrupts()</tt>during initialization.
  • Interrupting threads (via <tt>PR_Interrupt()</tt>) on threads blocked in I/O functions is implemented to various degrees on different platforms. The UNIX based platforms all implement the function though there may be up to a 5 second delay in processing the request.
  • The mechanism used to implement <tt>PR_Interrupt()</tt> on the pthreads versions of NSPR is flawed. No failure attributable to the flaw has shown up in any tests or products - yet. The specific area surrounding pthread's continuation thread has been both observed and empirically proven faulty, and a correction identified.

Macintosh

  • The current port of NSPR for Macintosh will not be usable in its own right. The implementation has dependencies on libraries that are implemented in the Netscape Communicator. The work to undo this dependency has already been started and the result will be made available soon.
  • Macintosh threads are not preemptable. As usual, it isn't that we couldn't preempt them, but rather that the Macintosh libraries are not prepared for such things to happen. NSPR does take advantage of Macintosh' asynchronous I/O capability to perform scheduling during I/O. Still, there is no enforceable mechanism to guarantee thread scheduling fairness.
  • Calendar time facilities are limited on Macintosh. One can only determine the immediate setting of Daylight Savings Time, not what it would be at some arbitrary time in the past or the future.
  • Initialization of NSPR may fail if the host is not connected to a network of some kind.

WIN-16

http://www.sybase.com/products/languages/watccpl.html

The WIN-16 port is unique in the NSPR world in that it uses the WATCOM development environment. This environment does bring along some of its own special trappings.

  • Win16 uses co-operative non-preemptive dispatching for its own processes. This means that when the application using NSPR is dispatched, a thread within that application will run until that thread voluntarily gives up control to another thread within the process. Further, the process in which the threads are being emulated will run until it invokes some function that gives control to Windows.
  • Windows 3.1 applications always run on a stack provided by Windows. This means that NSPR threads use a shadow stack to preserve context across a thread switch. At thread switch time, the stack of the current running thread is copied to other storage associated with that thread and the about-to-be-dispatched thread's stack data is copied back onto the Windows stack just before the thread is given control. This has disastrous implications on the generally accepted programming practice of taking the address of a stack variable and giving that address to another thread.

Consider that the Windows stack is swapped in and out with each thread switch. The thread that created a stack variable sees his own stack variables correctly across thread switches. However, another thread does not see the data as intended because the stack has been swapped out to the shadow stack and the current thread's stack now occupies the Windows stack. This can be difficult to diagnose.

For complete cross platform portability, do not take the address of a stack variable and make that address available to another thread. For more details, see the memo on Automatic Addresses.

  • Windows 3.1 does not support 64 bit file offsets. The NSPR 64 bit file offset APIs map to Windows 3.1 32 bit file offset APIs. You may use NSPR's 64bit file offset APIs in your Windows 3.1 applications for cross platform consistency, but do not expect to see real 64 bit file offset behavior. On Windows 3.1, when NSPR detects a value in a 64 bit file offset greater than 32bit significance, it terminates with an ASSERT.
  • The general rules of 16 bit Large Model memory restrictions apply to applications using NSPR on Windows 3.1.
  • The NSPR Process Creation API functions return an error when invoked on Windows 3.1.
  • Functions called in an application by a shared library require an additional function prolog. The prolog is presented in the macro <tt>PR_CALLBACK</tt>. For any function made available to any shared library (most likely passed in as a function pointer), that function must have the <tt>PR_CALLBACK</tt> qualifier.
  • NSPR functions are normally declared <tt>__cdecl</tt>. These declarations are hidden in the <tt>PR_EXTERN()</tt> declarations in NSPR's header files. NSPR functions returning floating point types and structs by value, including the derived types PRInt64, are declared <tt>__PASCAL</tt>. These different declarations are to accommodate restrictions in Watcom's C/C++ version 11.0 and 11.0a compiler.
  • Windows 3.1 does not provide a non-blocking file I/O interface. NSPR's file I/O is done using Windows 3.1's synchronous file I/O API. The NSPR file I/O APIs may cause a thread switch even though the I/O is fully synchronous. Do not depend on File I/O being synchronous.
  • Command line applications using the LIBC printf() and related APIs behave funny on Windows 3.1. Read the fine print in the Windows SDK for the grizzly details.
  • Watch out for PRIntn overflow on Win16. Make sure all possible values of your PRIntn variables are within 2^16.

WIN-95

The WIN-95 version of NSPR should really be labeled WIN-32. The same library will work on either WIN-NT or WIN-95.

  • WIN-95 NSPR uses native threads as NSPR threads. Generally speaking, the native threads (on NT or '95) are quite functional. However, having lots of them (100s or even 1000s) is unrealistic.
  • <tt>PR_Interrupt()</tt> is not implemented.

WIN-NT

The WIN-NT port of NSPR takes advantage of some of the features of NT that are not available in WIN-95, such as fibers and asynch I/O. The implementation is well suited for high performance application, such as a server, but clients may find the WIN-95 version more suited (and adequate) for interactive applications such as are prevalent on today's workstations.

  • The NT version implements a MxN threading model, using native NT threads as the virtual processors and NT's fiber abstraction as the locally scheduled threads. The fibers are nice because much of the NT API understands them. But they are not really preemptable, and that can lead to problems, depending on the needs of your application.
  • The mode (blocking or nonblocking) of a socket cannot be changed at will once the socket has been used. Also, the new socket created by an <tt>PR_Accept()</tt> call on a listening socket inherits the mode of the listening socket, and cannot be changed.
  • A file descriptor is not usable after an IO operation on it fails with either <tt>PR_IO_TIMEOUT_ERROR</tt> or <tt>PR_IO_INTERRUPT_ERROR</tt>. The only thing you can do is to close the file descriptor. See the FAQ article for more info. There is no current workaround. You must write your program with this restriction in mind.
  • PR_Interrupt is implemented except for <tt>PR_Connect()</tt>.

IRIX

  • The IRIX (classic) implementation has a known race condition in creating new threads. If the spawned thread establishes and exits before the parent thread is resumed, the parent will be left with an invalid pointer to the child. The problem has a solution that will be available soon.

Digital Unix

  • Digital Unix (aka, OSF1) requires the latest patches for the pthreads library. The changes are not evident until one tries to do thread suspension (<tt>PR_SuspendAll()</tt>) which is in preparation for garbage collection.
  • Digital Unix is the main platform used for NSPR's prototype use of IPv6. There are some conflicts between the patches needed to use IPv6 and those needed for pthreads. The IPv6 kit needs to be applied on a baseline configuration of Digital Unix V4.0B.

Linux

  • The pthreads version of Linux (which is not the default) uses the same signals that NSPR uses to suspend threads in preparation for garbage collection. No solution in hand.

OS/2

  • The OS/2 port is not functional in its current state due to the requirement to remove some files that could not be shipped under the NPL. This problem has been resolved and an update will be appearing shortly.

Original Document Information