0

In an IOCP Winsock2 client, after ConnectEx() times-out on an unsuccessful connection attempt, the following happens:

  1. An "IO completion" is queued to the associated IO Completion Port.

  2. GetQueuedCompletionStatus() returns FALSE.

  3. WSAGetOverlappedResult() returns WSAETIMEDOUT.

What determines the timeout period between calling ConnectEx() and 1 above? How can I shorten this timeout period?

I know that it is possible to wait for ConnectEx() by passing it a filled-out structure OVERLAPPED.hEvent = WSACreateEvent() and then waiting for this event, e.g. with WaitForSingleObject(Overlapped.hEvent, millisec) to timeout after no connection has been made for the millisec time period. BUT, that solution is outside the scope of this question because it does not refer to the IOCP notification model.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
George Robinson
  • 1,500
  • 9
  • 21
  • iocp this is not mode. and timeout value independent from associate you socket with iocp or not – RbMm Mar 03 '19 at 14:19
  • 1
    IOCP is a mode of completing the connection request made to Winsock. Anyway, what determines the "connect timeout" value of the socket, regardless whether it is associated with the IOCP or not ? – George Robinson Mar 03 '19 at 14:24
  • if be exactly - iocp this is not mode but only way how you get notification when io operation complete. i not know/view socket options which let set connection timeout, but let ask - for what you need this ? – RbMm Mar 03 '19 at 14:28
  • 1
    For fast TCP connection scanner, that detects ONLY quickly-connecting servers. – George Robinson Mar 03 '19 at 14:39
  • The default connect timeout is set by the platform, usually around a minute. You can decrease it, but not increase it, by using non-blocking mode and `select()`. – user207421 Mar 03 '19 at 14:47
  • not sure are exist some option (say via `setsockopt`) which let you set timeout, but possible ofcourse control this manual. say create timer for socket and start it after call `ConnectEx` or use `CreateTimerQueueTimer` – RbMm Mar 03 '19 at 14:49
  • 1
    Or continue using an Event object, use RegisterWaitForSingleObject() to wait on it, and if connected before it elapses then cancel the wait, otherwise call CancelIoEx on the OVERLAPPED – Remy Lebeau Mar 03 '19 at 17:40
  • Which method is easier on system resources? The one using `CreateTimerQueueTimer()` or the one using `RegisterWaitForSingleObject()` ? – George Robinson Mar 03 '19 at 22:00

1 Answers1

1

unfortunatelly look like no built-in option for set socket connect timeout. how minimum i not view this and based on this question - How to configure socket connect timeout - nobody not view too.

one possible solution pass event handle to I/O request and if we got ERROR_IO_PENDING - call RegisterWaitForSingleObject for this event. if this call will be successful - our WaitOrTimerCallback callback function will be called - or because I/O will be complete (with any final status) and at this moment event (which we pass both to I/O request and RegisterWaitForSingleObject) will be set or because timeout (dwMilliseconds) expired - in this case we need call CancelIoEx function.

so let say we have class IO_IRP : public OVERLAPPED which have reference counting (we need save pointer to OVERLAPPED used in I/O request for pass it to CancelIoEx. and need be sure that this OVERLAPPED still not used in another new I/O - so yet not free). in this case possible implementation:

class WaitTimeout
{
    IO_IRP* _Irp;
    HANDLE _hEvent, _WaitHandle, _hObject;

    static VOID CALLBACK WaitOrTimerCallback(
        __in  WaitTimeout* lpParameter,
        __in  BOOLEAN TimerOrWaitFired
        )
    {
        UnregisterWaitEx(lpParameter->_WaitHandle, NULL);

        if (TimerOrWaitFired)
        {
            // the lpOverlapped unique here (because we hold reference on it) - not used in any another I/O
            CancelIoEx(lpParameter->_hObject, lpParameter->_Irp);
        }

        delete lpParameter;
    }

    ~WaitTimeout()
    {
        if (_hEvent) CloseHandle(_hEvent);
        _Irp->Release();
    }

    WaitTimeout(IO_IRP* Irp, HANDLE hObject) : _hEvent(0), _Irp(Irp), _hObject(hObject)
    {
        Irp->AddRef();
    }

    BOOL Create(PHANDLE phEvent)
    {
        if (HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL))
        {
            *phEvent = hEvent;
            _hEvent = hEvent;

            return TRUE;
        }

        return FALSE;
    }
public:

    static WaitTimeout* Create(PHANDLE phEvent, IO_IRP* Irp, HANDLE hObject)
    {
        if (WaitTimeout* p = new WaitTimeout(Irp, hObject))
        {
            if (p->Create(phEvent))
            {
                return p;
            }

            delete p;
        }

        return NULL;
    }

    void Destroy()
    {
        delete this;
    }

    // can not access object after this call
    void SetTimeout(ULONG dwMilliseconds)
    {
        if (RegisterWaitForSingleObject(&_WaitHandle, _hEvent, 
            (WAITORTIMERCALLBACK)WaitOrTimerCallback, this, 
            dwMilliseconds, WT_EXECUTEONLYONCE|WT_EXECUTEINWAITTHREAD))
        {
            // WaitOrTimerCallback will be called
            // delete self here
            return ;
        }

        // fail register wait
        // just cancel i/o and delete self
        CancelIoEx(_hObject, _Irp);
        delete this;
    }
};

and use something like

if (IO_IRP* Irp = new IO_IRP(...))
{
    WaitTimeout* p = 0;

    if (dwMilliseconds)
    {
        if (!(p = WaitTimeout::Create(&Irp->hEvent, Irp, (HANDLE)socket)))
        {
            err = ERROR_NO_SYSTEM_RESOURCES;
        }
    }

    if (err == NOERROR)
    {
        DWORD dwBytes;

        err = ConnectEx(socket, RemoteAddress, RemoteAddressLength, 
            lpSendBuffer, dwSendDataLength, &dwBytes, Irp)) ?
                NOERROR : WSAGetLastError();
    }

    if (p)
    {
        if (err == ERROR_IO_PENDING)
        {
            p->SetTimeout(dwMilliseconds);
        }
        else
        {
            p->Destroy();
        }
    }

    Irp->CheckErrorCode(err);
}

another possible solution set timer via CreateTimerQueueTimer and if timer expired - call CancellIoEx or close I/O handle from here. difference with event solution - if I/O will be completed before timer expired - the WaitOrTimerCallback callback function will be not automatically called. in case event - I/O subsystem set event when I/O complete (after intial pending status) and thanks to that (event in signal state) callback will be called. but in case timer - no way pass it to io request as parameter (I/O accept only event handle). as result we need save pointer to timer object by self and manually free it when I/O complete. so here will be 2 pointer to timer object - one from pool (saved by CreateTimerQueueTimer) and one from our object (socket) class (we need it for dereference object when I/O complete). this require reference counting on object which incapsulate timer too. from another side we can use timer not for single I/O operation but for several I/O (because it not direct bind to some I/O)

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • 1
    You wrote: "Unfortunately it looks like there is no built-in option for setting socket connect timeout" ...and that IS an grim but acceptable answer to my second question. As to the presented workaround, I am wondering which method consumes less system resources: the one using CreateTimerQueueTimer() or the one using RegisterWaitForSingleObject() ? – George Robinson Mar 07 '19 at 13:11
  • @GeorgeRobinson - *less system resources* hard to say, also this is depend from windows version - very different implementations. for timer i use [this](https://github.com/rbmm/LIB/blob/master/ASIO/io.h#L248). also possible that you link all your sockets object to list and periodic (say 1 per second) check this list for timeout. begin connection time remember in every object. this take less resourses compare this 2 ways – RbMm Mar 07 '19 at 13:23