Can process IDs be greater than 64000? Because we’re seeing process IDs greater than 64000

A customer asked what to me was a very strange question.

Can process IDs be greater than 64000? Because we're seeing process IDs greater than 64000.

This is a strange question because the answer is right there: You're seeing process IDs greater than 64000 with your very own eyes. Do you doubt the evidence right there in front of you?

It's like asking, "Is it possible to have an orange with no seeds? Because I have an orange with no seeds."

We saw some time ago that process IDs can get very high indeed, although the kernel tries to keep the numbers small purely for cosmetic reasons.

The customer explained why they were asking this question.

Our application is crashing when the process ID gets too large. Here is the code:

int eventId = System.Diagnostics.Process.GetCurrentProcess().Id;

Okay, um, that code makes no sense.

The code uses the process ID as the event ID. But event IDs are static; they are references to messages in your event source's message table. It's not like your message table contains 65535 entries like this:

An event occurred in Process ID 1: %1

An event occurred in Process ID 2: %1

An event occurred in process ID 3: %1

… and so on until …

An event occurred in process ID 65535: %1

The event ID describes what happened, and the process ID and other information goes into the payload.

You can see from the format of event IDs that the event number can be at most 65535 because the upper bits of the event ID are used to encode other information.

And the code crashes because the Write­Entry method specifically checks for absurd event IDs and rejects them with an Argument­Exception.

The fix is therefore to put the process ID in the payload of your event and let the event number describe what actually happened, like "A multi-part print job was created."

Comments (25)
  1. AC says:

    This indeed makes no sense at all. He notices that large event IDs crash his program and assumes that the IDs are the cause and not just the trigger.

    He never bothered to check that his code doesn't work even in the case where it doesn't crash. It compiles, it runs, perfect.

  2. skSdnW says:

    #define INVALID_PROCESSID ASFW_ANY if you need such a thing, otherwise treat it as a 32bit number, how hard can it be? ;)

    Why did WinAPI usermode thread/process id's stay 32 bit when the kernel changed their version to "size_t"?

  3. Adam Rosenfield says:

    @skSndW: For backwards compatibility, I'd wager.  How do you possibly change a function like GetCurrentProcessId() from returning a 32-bit value to returning a 64-bit value without breaking binary compatibility with existing programs?  And of course source compatibility, too.

  4. Mark says:

    And can you imagine using task manager with 64-bit numbers in there?

  5. Peter says:

    I always try to be charitable when reading Raymond's blog.  I think to myself, "surely, the poor, beleagered customer was just missing some key piece of information that lead him astray."  But sometimes, I just can't help thinking, "How do these people even have a job?"

  6. Simon Farnsworth says:

    The only charitable interpretation I can come up with for this customer's confusion is that the customer's original request is unclear (whether that's customer or customer liaison on the route to Raymond is up for debate); possibly they meant "I'm seeing process IDs greater than 64,000; I was under the impression that process IDs cannot exceed 64,000, and would like help determining whether these large PIDs are a sign of a bug in my code, or if I need to handle them".

    I can see how a customer who assumed that PIDs can't be greater than 64,000 would come up with the original problem statement.

  7. skSdnW says:

    @Adam Rosenfield: I was talking about 64 bit processes, wow64 would still be 32 bit of course.

    It would prevent 32 bit processes calling OpenProcess on high PID's but the kernel guys made the change and since usermode did not the only way passed 4 billion threads now is a new NT subsystem.

  8. Joshua says:

    Well if the NT team were to assert that ProcessId is always < 64000, they would know they were looking for a memory corruption bug.

  9. morlamweb says:

    My question is, how did the customer get the false assumption that process IDs can't be higher than 64,000?  Have they not seen a busy system?  In my work, I tend not to make assumptions about OS objects like process identifiers.  I start a process, it gets a numerical ID from the OS; when I query for the ID, I just make sure that it fits into the data type that I throw it into.  None of this "must be less than an arbitrary limit" or "must be divisible by 2 / 4/ whatever" foolishness.  I've seen several long-running Windows systems that have processes with IDs in the 6 decimal digit range.

    Pre-emptive snarky comment: "long-running Windows system" is what, 3 days?  Pre-emptive answer: hardly.  My Windows 7 clients regularly stay up for more than a month, and would be running longer were it not for IT-mandated updates.  And Windows NT4 and 2000?  Embedded systems running those OSs have run for years.

  10. Adam Rosenfield says:

    @skSdnW: So was I.  Imagine I compile a 64-bit program and ship it off to the world.  That program calls GetCurrentProcessId or OpenProcess, passing 32-bit values back and forth with kernel32.dll.  It works great on existing Windows versions.

    Now imagine that Windows 9 comes out.  How could Windows 9's version of kernel32.dll possibly return a 64-bit value to my program?  The only way would to do so be to introduce a new API (say, GetCurrentProcessId64() or GetCurrentProcessIdEx() etc.) and deprecate the old API; but the old API would need to stick around for old programs which aren't getting recompiled.

  11. skSdnW says:

    @Adam Rosenfield: The ship has already sailed which is why I said that we need a new usermode subsystem to move past 32 bit.

    My question is, why was GetCurrentProcessId not changed when everything else in the WinAPI moved to INT/LONG/DWORD_PTR during porting to IA64. And if 32 bit is enough for everybody, why is the kernels CLIENT_ID struct not 32 bit as well?

    [How would you marshal a 64-bit process ID to a 32-bit process? Process IDs are global, so they are meaningful to pass between processes. -Raymond]
  12. alegr1 says:

    >My Windows 7 clients regularly stay up for more than a month

    I see you're not using MS security suite, which is causing memory leak in the kernel. Though even without it, there is CONFIGMGR memory leak.

  13. skSdnW says:

    @Raymond: Yes, that is a problem. WOW64 is not perfect and a 32bit application on a 64 bit host is never going to be as good as a native app if we are talking task mangers/debugging tools etc but in general, how much of a problem would it be? If CreateProcess in a 32bit app makes sure the PID fits in 32 bits that would probably take care of most issues.

    If they are global across all NT subsystems then why did CLIENT_ID have to grow? Kernel only worker threads would be the only possible thing that could ever have such a high TID.

    [You often need to communicate with an application you didn't yourself launch. It may have been launched indirectly via DCOM, or you may have done a FindWindow to find it, or you might be doing UI automation. (And I don't know why CLIENT_ID grew, since it didn't need to. My guess is that for the same reason process IDs are a multiple of 4.) -Raymond]
  14. morlamweb says:

    @alegr1: no, I'm not using MS' security suite.  I'm using IT's mandated security suite from another vendor.  I use these machines (Win7 32- and 64-bit boxes, one each) daily for work and only have to reboot for required Windows updates.

  15. Zan Lynx' says:

    The programmer might have been used to Unix or Linux systems where the PID is normally limited to 32,768.

    This was also for backward compatibility when programs assumed PIDs would all fit into signed 16-bit integers. These days it can be changed to be much larger.

    It might be interesting to find out how Windows managed the transition from 16-bit PIDs to 32-bit. And why wouldn't the same method work to migrate to a 64-bit value?

    [The transition was trivial because 16-bit Windows didn't have PIDs. -Raymond]
  16. Joshua says:

    [The transition was trivial because 16-bit Windows didn't have PIDs. -Raymond]

    getpid() returns SOMETHING. (And yes it really is called getpid. Look in the 16 bit C library.) I always thought it was the segment address of the PSP or something stupid like that.

    [getpid is not part of the 16-bit Windows API. It's something invented by the C runtime folks to mimic unix. I have no clue what they used. -Raymond]
  17. perhaps it's a documentation issue says:

    To be fair to the customer, it doesn't look like EventLog.WriteEntry() actually makes use of the message table resource as far as I can tell, looking at the same documentations the customer probably looked at:…/e29k5ebc(v=vs.110).aspx…/307024

    Seems like this method just writes the string directly; it looks like it is actually EventLog.WriteEvent() that uses the message table, if I understand the documentation correctly.  Therefore I can see how a customer may mistakenly just think the eventId is just an arbitrary value they can use any way they like, since essentially the documentation available doesn't really seem to provide any evidence otherwise.

    That doesn't excuse the customer since the documentation clearly points out the supported range of eventId values and the resulting exception type, and it doesn't take too many brain cells to realize the solution of putting the process ID into the string instead of shoehorning it into eventId.  (But even then, I can see the customer may have authored existing tools for processing event log entries for which such a change would be a breaking change they would like to avoid if possible, hence the odd question.)

    Bottom line: okay yes it is a very strange question, but now I looked at it more carefully, maybe the customer isn't quite as stupid as it seems?  [And then I see they asked about 64000 rather than 65535 or 65536 and I lose my respect again ;p]

  18. Ian Boyd says:

    "Is broccoli supposed to be white? Because I have some white broccoli here."

    "Uh, no. That's cauliflower."

  19. Engywuck says:

    [And then I see they asked about 64000 rather than 65535 or 65536 and I lose my respect again ;p] — well, perhaps the customer asked about "64k" and the support person changed it to 64000? ;-)

  20. The_Assimilator says:

    Although the person who asked the question is obviously clueless, this problem wouldn't exist at all if the EventLog.WriteEntry() method's parameters were defined correctly. Currently the eventId parameter is defined as an Int32, but the documentation says that the method will throw an ArgumentException if "eventID is less than zero or greater than UInt16.MaxValue". Well guess what data type is guaranteed to be between 0 and 65535… you guessed it, UInt16. So why is eventId a UInt16 and not an Int32?

    Yes, I know there's the old "unsigned types aren't CLS-compliant because some CLR languages don't support unsigned types" but that's always been a stupid rule. Why not simply declare the affending languages non-CLS-compliant instead of foisting their cruft on everyone else?

  21. Katie says:

    @perhaps it's a documentation issue

    Well that helps explain why they thought the code works at all. Because they sent a specific string, the ID wasn't needed to lookup an event in the table. In the event viewer, the user can see the message and the event ID, so all the information the customer wanted does show up in the event viewer. Of course, it's showing up in completely the wrong way, and the end-user won't be able to do things like filter on the event ID to find all instances of a specific type of error, but it does get the error message and the process ID in the log.

  22. John Elliott says:

    getpid() in 16-bit Windows is a wrapper for GetCurrentTask() (exported from KERNEL.EXE).

  23. Mike Dimmick says:

    @The_Assimilator: The 1.x versions of Visual Basic .NET did not handle non-CLS-compliant types (specifically, they couldn't handle unsigned types, except for Byte, and didn't support signed 8-bit values). EventLog.WriteEntry dates back to .NET 1.0, and presumably uses Int32 for that parameter so that Visual Basic could call it.

  24. The_Assimilator says:

    @Mike Dimmick: As I said, I know all of the original reasons for this. But the fact is that it's 2014 and the CLI specification (ECMA-335) STILL classifies unsigned types as non-CLS-compliant is farcical. Granted this would be a breaking change, .NET 1.0 is not even supported by Microsoft anymore, so it's effectively frozen – hence it would be easy to make this change in the newer versions of the framework, because the old versions would remain unaffected.

    As for EventLog.WriteEntry() – there's no reason an overload that takes a UInt16 can't be created for future .NET versions, and the current version marked as [Obsolete]. Then new code will pass in UInt16, current code that passes in UInt16 will work, and current code that passes in Int32 will still work but will get a compiler warning.

Comments are closed.