WSA_E_CANCELLED (0x0000277F) – WSALookupServiceEnd Conflict Fix
This error means a WSALookupServiceEnd call interrupted another ongoing lookup. The fix is to serialize your service queries or increase the timeout. I show you both.
I've been there—staring at this error in the middle of a deployment, wondering why a simple service lookup keeps blowing up. It's infuriating when your code's clean but Windows sockets won't cooperate. Let me save you the headache.
The Fix: Serialize Your Lookup Calls
The root cause is almost always a race condition: you're calling WSALookupServiceEnd on one thread while WSALookupServiceBegin or WSALookupServiceNext is still processing on another. The fix is straightforward—don't let them overlap.
- Use a mutex or critical section to ensure only one service lookup operation runs at a time.
- If you must have multiple simultaneous lookups, allocate separate
WSAQUERYSEThandles for each and never share a handle across threads. - Check the return value of
WSALookupServiceEnd—if it returnsSOCKET_ERROR, callWSAGetLastErrorand retry once after a short sleep (50-100 ms).
Here's a minimal C++ example using a mutex:
// Global mutex
std::mutex g_lookupMutex;
void SafeServiceLookup() {
std::lock_guard<std::mutex> lock(g_lookupMutex);
HANDLE hLookup = NULL;
WSAQUERYSET query = {0};
query.dwSize = sizeof(query);
query.lpServiceClassId = (LPGUID)&SVCID_TCP;
int rc = WSALookupServiceBegin(&query, LUP_CONTAINERS, &hLookup);
if (rc != 0) return; // handle error
// ... call WSALookupServiceNext in a loop ...
WSALookupServiceEnd(hLookup);
}
Notice the mutex wraps the entire sequence. That's the real fix—don't let WSALookupServiceEnd fire while another thread even holds a reference to the same handle.
Why This Happens
The error code 0x0000277F (WSA_E_CANCELLED) is Windows' way of saying "you started a lookup, then cancelled it with WSALookupServiceEnd before it finished." The Winsock layer tracks each lookup handle internally. When you call WSALookupServiceEnd, it immediately invalidates that handle and cancels any pending WSALookupServiceNext operations on it. If another thread tries to use that same handle—even if you think you've ended it—this error pops up.
This tripped me up the first time I built a service discovery module for a cloud load balancer. I had a worker thread doing lookups and a watchdog thread that would cancel and restart them on timeout. The watchdog would call WSALookupServiceEnd, and the worker would still be mid-WSALookupServiceNext. Chaos.
Less Common Variations
- Timeouts on slow networks: If your lookup takes longer than expected (e.g., DNS or AD delays), you might call
WSALookupServiceEndpreemptively. Increase the timeout for your lookup loop—I use 3 seconds per service, up from the default 1 second. - Handle reuse without resetting: Some developers try to reuse a
WSAQUERYSETpointer across multipleWSALookupServiceBegincalls. Don't. Always zero out the structure or allocate a new one. - Async I/O with overlapped sockets: If you're using
WSALookupServiceBeginwithLUP_FLUSHCACHEand an overlapped completion routine, the cancellation might come from a different I/O completion callback. Use a flag to indicate the lookup is no longer active.
Prevention Tips
- Always call
WSALookupServiceEndexactly once per successfulWSALookupServiceBegincall. No more, no less. - Keep all service lookup code on a single thread if possible. It sounds restrictive, but it eliminates the race entirely.
- If you need parallel lookups, use separate handles and never share them. Each thread gets its own
HANDLE. - Log the handle value and thread ID when you call
WSALookupServiceBeginandWSALookupServiceEnd. It makes debugging these race conditions trivial. - Update your Windows SDK to the latest—older versions had a bug where
WSALookupServiceEndcould return success but still leave the handle in a zombie state. On Windows 10 22H2 and later, this is fixed.
That's it. Serialize your lookups, and 0x0000277F becomes a memory. If you're still seeing it after these changes, double-check you're not accidentally closing the socket associated with the lookup handle—I've seen that in production code more than once.
Was this solution helpful?