acl-dev / acl

C/C++ server and network library, including coroutine,redis client,http/https/websocket,mqtt, mysql/postgresql/sqlite client with C/C++ for Linux, Android, iOS, MacOS, Windows, etc..
https://acl-dev.cn
GNU Lesser General Public License v3.0
2.88k stars 936 forks source link

improve fiber_io_loop #279

Open sidyhe opened 2 years ago

sidyhe commented 2 years ago

鉴于之前timer变量未更新的问题, 研究了一下io loop逻辑 发现即使需要更久的io超时, 也会把它限制在100毫秒以内 去掉此限制后, 程序却不会正常退出, 因为最后一个fiber无io无timer却会执行event_process且无期限(timeout == -1) 所以loop逻辑不够完善, 遂重写了一遍 添加了acl_fiber_nready函数 (所以之前fiber从创建到被调度是有额外延时的?) 重写之后会做最合适的超时判定, 不会再被无意义的唤醒, 减少了冗余代码 没有提PR的原因是刚写好, 尚未经过一定时间的验证, 也想请作者review一下

static void fiber_io_loop(ACL_FIBER *self fiber_unused, void *ctx) {
    EVENT *ev = (EVENT *) ctx;
    ACL_FIBER *timer;
    long long now;
    int timeout;

    fiber_system();

    for (;;) {
        // run any ready fiber
        while (acl_fiber_yield() > 0) { }

        if (__thread_fiber->io_stop) {
            break;
        }

        if (acl_fiber_nready() > 0) {
            timeout = 0; // will be check event no wait

            // maybe some timer expired
            timer = FIRST_FIBER(&__thread_fiber->ev_timer);
        } else {
            timer = FIRST_FIBER(&__thread_fiber->ev_timer);
            if (timer) {
                now = event_get_stamp(__thread_fiber->event);

                if (now >= timer->when) {
                    timeout = 0; // some timer expired
                } else {
                    long long diff = timer->when - now;

                    if (diff <= INT_MAX) {
                        timeout = (int)diff;
                    } else {
                        timeout = 1000;
                        msg_warn("%s(%d), too large timer! tid: %lu, ms: %lld",
                                 __FILE__, __LINE__, __pthread_self(), diff);
                    }
                }
            } else {
                // no timer, check any io exist
                if (ev->fdcount > 0 || ev->waiter > 0 || ring_size(&ev->events) > 0) {
                    timeout = -1;
                } else {
                    // nothing to go, finished
                    break; // for
                }
            }
        }

        event_process(ev, timeout);

        if (timer) {
            now = event_get_stamp(__thread_fiber->event);

            while (timer && now >= timer->when) {
                ring_detach(&timer->me);

                if (!timer->sys && --__thread_fiber->nsleeping == 0) {
                    fiber_count_dec();
                }

                timer->status = FIBER_STATUS_NONE;
                acl_fiber_ready(timer); // mark
                timer = FIRST_FIBER(&__thread_fiber->ev_timer);
            }
        }
    } // for

    msg_info("%s(%d), tid=%lu, IO fiber exit! fdcount=%d, waiter=%u, events=%d, io_count=%d",
             __FILE__, __LINE__, __pthread_self(),
             ev->fdcount, ev->waiter, ring_size(&ev->events), (int) __thread_fiber->io_count);

    // don't set ev_fiber NULL here, using fiber_io_clear() to set it NULL
    // in acl_fiber_schedule() after scheduling finished.
    //
    // __thread_fiber->ev_fiber = NULL;
}
int event_process(EVENT *ev, int timeout) {
    int ret;

    if (timeout < 0) {
        timeout = ev->timeout;
    } else if (ev->timeout >= 0) {
        if (ev->timeout < timeout) {
            timeout = ev->timeout;
        }
    }

#ifndef NDEBUG
    // make sure INFINITE
    assert(timeout >= 0 || timeout == -1);
#endif

    event_prepare(ev);

    // other code ...

    return ret;
}
zhengshuxin commented 2 years ago

之所以没将将超时设为-1,就是防止没有句柄被监听的情况。另外,该超时值还会被动态地修改,比如在 hooked poll中。

sidyhe commented 2 years ago

是的, 一共有两个超时判定 io loop的timeout和ev->timeout

分层来看 io loop只关心io和timer综合后应当timeout的值 event只关心上层给的timeout和自己的timeout谁更小

整体来看 所以io loop提供的timeout只是一个建议的值 event_process会把两个timeout做比较, 用最小的那个

你所说的动态修改是指ev->timeout吧, 看到poll和fiber delay会改它 又review了一遍代码, 应该没啥问题?

zhengshuxin commented 2 years ago

还需要拿一些例子去验证一下,另外,acl_fiber_nready 函数中需要把所有准备好的协程都得要包含进去。

sidyhe commented 2 years ago
unsigned acl_fiber_nready(void) {
    if (__thread_fiber == NULL) {
        return 0;
    }
    return (unsigned) ring_size(&__thread_fiber->ready);
}

是根据ndead仿照的, 是否有遗漏的情况呢