// File: /workspace/github_c_makefile_star2_O3/nilyang[P]redis-tdd-annotation/src/networking.c
// Function: sendReplyToClient
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
    redisClient *c = privdata;
    int nwritten = 0, totwritten = 0, objlen;
    size_t objmem;
    robj *o;
    REDIS_NOTUSED(el);
    REDIS_NOTUSED(mask);

    // 一直循环，直到回复缓冲区为空
    // 或者指定条件满足为止
    while(c->bufpos > 0 || listLength(c->reply)) {

        if (c->bufpos > 0) {

            // c->bufpos > 0

            // 写入内容到套接字
            // c->sentlen 是用来处理 short write 的
            // 当出现 short write ，导致写入未能一次完成时，
            // c->buf+c->sentlen 就会偏移到正确（未写入）内容的位置上。
            nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
            // 出错则跳出
            if (nwritten <= 0) break;
            // 成功写入则更新写入计数器变量
            c->sentlen += nwritten;
            totwritten += nwritten;

            /* If the buffer was sent, set bufpos to zero to continue with
             * the remainder of the reply. */
            // 如果缓冲区中的内容已经全部写入完毕
            // 那么清空客户端的两个计数器变量
            if (c->sentlen == c->bufpos) {
                c->bufpos = 0;
                c->sentlen = 0;
            }
        } else {

            // listLength(c->reply) != 0

            // 取出位于链表最前面的对象
            o = listNodeValue(listFirst(c->reply));
            objlen = sdslen(o->ptr);
            objmem = getStringObjectSdsUsedMemory(o);

            // 略过空对象
            if (objlen == 0) {
                listDelNode(c->reply,listFirst(c->reply));
                c->reply_bytes -= objmem;
                continue;
            }

            // 写入内容到套接字
            // c->sentlen 是用来处理 short write 的
            // 当出现 short write ，导致写入未能一次完成时，
            // c->buf+c->sentlen 就会偏移到正确（未写入）内容的位置上。
            nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen);
            // 写入出错则跳出
            if (nwritten <= 0) break;
            // 成功写入则更新写入计数器变量
            c->sentlen += nwritten;
            totwritten += nwritten;

            /* If we fully sent the object on head go to the next one */
            // 如果缓冲区内容全部写入完毕，那么删除已写入完毕的节点
            if (c->sentlen == objlen) {
                listDelNode(c->reply,listFirst(c->reply));
                c->sentlen = 0;
                c->reply_bytes -= objmem;
            }
        }
        /* Note that we avoid to send more than REDIS_MAX_WRITE_PER_EVENT
         * bytes, in a single threaded server it's a good idea to serve
         * other clients as well, even if a very large request comes from
         * super fast link that is always able to accept data (in real world
         * scenario think about 'KEYS *' against the loopback interface).
         *
         * 为了避免一个非常大的回复独占服务器，
         * 当写入的总数量大于 REDIS_MAX_WRITE_PER_EVENT ，
         * 临时中断写入，将处理时间让给其他客户端，
         * 剩余的内容等下次写入就绪再继续写入
         *
         * However if we are over the maxmemory limit we ignore that and
         * just deliver as much data as it is possible to deliver. 
         *
         * 不过，如果服务器的内存占用已经超过了限制，
         * 那么为了将回复缓冲区中的内容尽快写入给客户端，
         * 然后释放回复缓冲区的空间来回收内存，
         * 这时即使写入量超过了 REDIS_MAX_WRITE_PER_EVENT ，
         * 程序也继续进行写入
         */
        if (totwritten > REDIS_MAX_WRITE_PER_EVENT &&
            (server.maxmemory == 0 ||
             zmalloc_used_memory() < server.maxmemory)) break;
    }

    // 写入出错检查
    if (nwritten == -1) {
        if (errno == EAGAIN) {
            nwritten = 0;
        } else {
            redisLog(REDIS_VERBOSE,
                "Error writing to client: %s", strerror(errno));
            freeClient(c);
            return;
        }
    }

    if (totwritten > 0) {
        /* For clients representing masters we don't count sending data
         * as an interaction, since we always send REPLCONF ACK commands
         * that take some time to just fill the socket output buffer.
         * We just rely on data / pings received for timeout detection. */
        if (!(c->flags & REDIS_MASTER)) c->lastinteraction = server.unixtime;
    }
    if (c->bufpos == 0 && listLength(c->reply) == 0) {
        c->sentlen = 0;

        // 删除 write handler
        aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);

        /* Close connection after entire reply has been sent. */
        // 如果指定了写入之后关闭客户端 FLAG ，那么关闭客户端
        if (c->flags & REDIS_CLOSE_AFTER_REPLY) freeClient(c);
    }
}

// Assembly:
pushq %rbp
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbx
subq $0x18, %rsp
movq %rdx, %rbx
movl %esi, 0xc(%rsp)
leaq 0x174(%rdx), %rax
movq %rax, 0x10(%rsp)
xorl %r13d, %r13d
xorl %r14d, %r14d
movl 0x170(%rbx), %eax
testl %eax, %eax
jg 0x2d442
movq 0x60(%rbx), %rax
cmpq $0x0, 0x28(%rax)
je 0x2d50e
movq (%rax), %rax
movq 0x10(%rax), %r12
movq 0x8(%r12), %rax
movslq -0x8(%rax), %rbp
movq %r12, %rdi
callq 0x2cf7d
movq %rax, %r15
testq %rbp, %rbp
jne 0x2d4c2
movq 0x60(%rbx), %rdi
movq (%rdi), %rsi
callq 0x205b1
subq %r15, 0x68(%rbx)
jmp 0x2d3f3
movslq 0x70(%rbx), %rcx
movq 0x10(%rsp), %rdx
leaq (%rdx,%rcx), %rsi
movslq %eax, %rdx
subq %rcx, %rdx
movl 0xc(%rsp), %edi
callq 0x1fb30
movq %rax, %r14
testl %r14d, %r14d
jle 0x2d50e
movl 0x70(%rbx), %eax
addl %r14d, %eax
movl %eax, 0x70(%rbx)
cmpl 0x170(%rbx), %eax
jne 0x2d486
xorl %eax, %eax
movl %eax, 0x170(%rbx)
movl %eax, 0x70(%rbx)
addl %r14d, %r13d
cmpl $0x10001, %r13d         # imm = 0x10001
jl 0x2d3f3
leaq 0x9cb0b(%rip), %rax     # 0xc9fa8
cmpq $0x0, 0x720(%rax)
je 0x2d525
callq 0x29b5e
leaq 0x9caf5(%rip), %rcx     # 0xc9fa8
cmpq 0x720(%rcx), %rax
jae 0x2d3f3
jmp 0x2d525
movslq 0x70(%rbx), %rax
movq 0x8(%r12), %rsi
addq %rax, %rsi
movq %rbp, %rdx
subq %rax, %rdx
movl 0xc(%rsp), %edi
callq 0x1fb30
movq %rax, %r14
testl %r14d, %r14d
jle 0x2d50e
movl 0x70(%rbx), %eax
addl %r14d, %eax
movl %eax, 0x70(%rbx)
cmpl %ebp, %eax
jne 0x2d486
movq 0x60(%rbx), %rdi
movq (%rdi), %rsi
callq 0x205b1
movl $0x0, 0x70(%rbx)
subq %r15, 0x68(%rbx)
jmp 0x2d486
cmpl $-0x1, %r14d
jne 0x2d520
callq 0x1f810
movl (%rax), %edi
cmpl $0xb, %edi
jne 0x2d58d
testl %r13d, %r13d
jle 0x2d543
testb $0x2, 0x90(%rbx)
jne 0x2d543
leaq 0x9ca73(%rip), %rax     # 0xc9fa8
movq 0x798(%rax), %rax
movq %rax, 0x80(%rbx)
cmpl $0x0, 0x170(%rbx)
jne 0x2d57e
movq 0x60(%rbx), %rax
cmpq $0x0, 0x28(%rax)
jne 0x2d57e
movl $0x0, 0x70(%rbx)
leaq 0x9ca43(%rip), %rax     # 0xc9fa8
movq 0x28(%rax), %rdi
movl (%rbx), %esi
movl $0x2, %edx
callq 0x20ce8
testb $0x40, 0x90(%rbx)
jne 0x2d5a8
addq $0x18, %rsp
popq %rbx
popq %r12
popq %r13
popq %r14
popq %r15
popq %rbp
retq
callq 0x1f630
leaq 0x698ec(%rip), %rsi     # 0x96e85
movl $0x1, %edi
movq %rax, %rdx
xorl %eax, %eax
callq 0x23223
movq %rbx, %rdi
addq $0x18, %rsp
popq %rbx
popq %r12
popq %r13
popq %r14
popq %r15
popq %rbp
jmp 0x2e517