While working on a hobby project, I set up an OS/2 MCP2 (Convenience Package 2 for OS/2 Warp 4) virtual machine with a debug kernel and an expectation that I’d reboot the VM a lot. I was disturbed to find that there was a consistent 30-second delay on boot while setting up networking, which accounted for something like two thirds of the VM’s boot-up time.
At first I thought maybe the DHCP client was being silly, or perhaps there could be a bug in the NIC emulation. Upon closer look, it turned out that the delay was happening in the MPTSTART.CMD script, which looks like this:
@ECHO OFF
IF NOT EXIST C:\MPTN\BIN\SETUP.CMD GOTO NBSETUP
INETWAIT 1>NUL
IF ERRORLEVEL 1 GOTO END
CALL C:\MPTN\BIN\SETUP.CMD
:NBSETUP
IF NOT EXIST C:\MPTN\BIN\NBSETUP.CMD GOTO END
CALL C:\MPTN\BIN\NBSETUP.CMD
:END
The referenced SETUP.CMD script does exist on the system, and NBSETUP.CMD does not. The 30-second delay was occurring in the somewhat mysterious INETWAIT command.
I could not find much about INETWAIT command on the web, although its purpose is more or less clear. It is meant to wait for the TCP/IP stack to be ready before it can be properly configured.
On closer look, the command actually is documented in the TCP/IP Command Reference that comes with MCP2. The command “causes the current program to wait until either the binding process between the first interface driver and the TCP/IP stack is complete, or the timer expires”. It was apparently added in TCP/IP version 4.1.
The INETWAIT usage help shows the following:
[C:\mptn\bin]inetwait -?
Usage: Inetwait [wait_time retries]
default wait_time=10000 (in milliseconds), retries=3
Okay, so by default, INETWAIT should wait 10 seconds, retrying up to three times, for a total of up to 30 seconds. But… why does it wait the full 30 seconds? Surely everything must be ready after the first 10-second wait? Networking did work in the VM and it could not possibly take more than 20 seconds to initialize everything…
Since the VM was already set up for debugging, it was not hard to break into the debugger during the 30-second wait and see what the INETWAIT process was up to. What I found was a little unexpected.
To communicate with the TCP/IP stack, INETWAIT uses the TCPIP32.DLL dynamic library. I believe it uses the SIOCGIFBOUND IOCTL to find out if network interfaces are bound to the TCP/IP stack. Since setting up a network interface may take some amount of time, interfaces may not be immediately ready, and INETWAIT is meant to give a bit of time until they come online–only not too much time, because they might not (maybe a cable is not plugged in, maybe a PCMCIA NIC isn’t inserted, etc.).
Sounds reasonable. Except… in the _DLL_InitTerm routine of TCPIP32.DLL (that is, code which gets executed while the DLL is being loaded), there is a check for TCP/IP stack readiness, and if that fails, the _DLL_InitTerm routine sleeps for 30 seconds.
As far as I can tell, the logic in TCPIP32.DLL completely defeats the purpose of INETWAIT. If the TCP/IP stack is ready, there will be no waiting; but if it’s not, TCPIP32.DLL will wait for 30 seconds before INETWAIT gets to do any checking or waiting.
The upshot is that either INETWAIT won’t need to wait at all, or if it does, it will most likely just add a useless extra delay in a situation where the TCP/IP stack is not going to be operational after any amount of waiting.
The behavior of TCPIP32.DLL has all the hallmarks of a quick and poorly thought out fix. Sleeping for 30 seconds in a DLL initialization routine is a terrible idea. The waiting should be done elsewhere (like INETWAIT!), but I expect it did fix some problem somewhere.
I should add that the behavior with an undesirable 30-second delay is quite system and configuration specific. It depends on how the system is configured, what hardware it uses, and how fast it runs. On many or perhaps most systems this issue probably won’t be visible because everything will be ready by the time INETWAIT runs, and neither TCPIP32.DLL nor INETWAIT will do any waiting.
I worked around the problem by adding a 1-second delay just before calling INETWAIT. That was enough to avoid the 30-second sleep in TCPIP32.DLL, as well as any waiting in INETWAIT itself, obviously. The boot time went down from about 45 seconds to 15 seconds, which is quite a difference.