#concurrency
Posts
-
Digging into Gradle's Source 9: The Task Graph and the Execution Plan — What, in What Order, How Many at Once
How does the pA in gradle pA become packageApp? What do dependsOn, mustRunAfter, shouldRunAfter and finalizedBy turn into inside the execution plan, and who decides the order? Who picks the next node while the build runs, what exactly do --parallel and --max-workers limit, and how does the Worker API split up the work inside a single task? We answer each question from the source at a fixed commit and from examples we actually ran (whenReady, --dry-run, thread names, jstack).
-
Digging into Kafka's Source 4: The Broker's Network Layer — Acceptors, Processors and Request Handlers
We follow, in SocketServer.scala, how the three kinds of threads in a broker's jstack — socket-acceptor, network-thread and request-handler — share the work of a single request. One Acceptor per listener; a Processor reads the bytes, puts the request on the request queue and mutes the connection; a request handler branches on KafkaApis.handle()'s match; and the response goes back to the Processor that read the request. With jstack, a raw-socket client and the request log we check how mute/unmute keeps one connection's requests in order by handling them one at a time, and where num.network.threads, num.io.threads and queued.max.requests take effect, and we compare it all with Tomcat's NIO connector.
-
Digging into Kafka's Source 5: Inside the Producer — From send() to Batches and the Sender Thread
KafkaProducer.send() sends nothing over the network. It serializes the record, picks a partition, appends it to a per-partition batch in the RecordAccumulator and hands back a Future; the one thread that actually sends is kafka-producer-network-thread. We follow in the source the if in ready() where linger.ms and batch.size apply, the built-in partitioner that sticks keyless records to one partition, the BufferPool that hands out batch memory and max.block.ms, retries and delivery.timeout.ms, and max.in.flight.requests.per.connection, then check them with examples that count batches with kafka-dump-log.sh and print which thread runs the callback.
-
Digging into MariaDB's Source 4: Connections and Threads — One Thread per Connection, and the Thread Pool
We follow the source from the moment mariadbd's main thread accepts a connection with poll() and wraps it in a CONNECT, to the point where it hands it to the scheduler (a bundle of function pointers) chosen by thread_handling. We look at the default one-thread-per-connection mode and its thread cache, the login step that creates the THD and authenticates, and how the thread pool — thread groups, a listener, workers and a timer_thread — detaches idle connections from threads, checking each step with ps, information_schema and gdb, and comparing it with httpd's MPMs and Tomcat's endpoint.
-
Digging into nginx's Source 6: Master and Workers — Processes, Signals and Zero-Downtime Reload
We follow src/os/unix/ngx_process_cycle.c to see how one nginx -s reload wakes the master's sigsuspend() loop, starts new workers with the new config, and lets the old workers finish what they were doing before they leave. Why the signal handler only sets a flag, the socketpair channel between master and workers, the respawn, just_spawn and exiting bits in the ngx_processes[] table, how a worker killed with kill -9 comes back, and the USR2 binary upgrade — all checked with strace, debug logs and ps.
-
Digging into nginx's Source 7: The Event Loop — epoll, Timers, Posted Events and accept
We follow one turn of ngx_process_events_and_timers(), the loop that lets a single-threaded worker juggle thousands of connections. How epoll is plugged in through a function-pointer table (ngx_event_actions), the connection array pre-built to worker_connections slots and its free list, EPOLLET and the instance bit that filters stale events, how the red-black timer tree becomes epoll_wait's timeout, the posted queues, and which of accept_mutex (off by default), EPOLLEXCLUSIVE and reuseport decides who accepts a new connection — checked with strace, gdb and the debug log.
-
Digging into Apache httpd's Source 8: MPMs — How prefork, worker and event Share Out Connections
Follows in the source what the single line LoadModule mpm_event_module in httpd.conf actually changes. How an MPM can be swapped as one module (the mpm hook and ap_mpm_query), prefork that only uses processes, worker that splits the job between a listener thread and a queue, event that takes keep-alive connections off the threads and hands them to epoll, the parent's once-a-second loop that grows and shrinks the children, the shared-memory scoreboard, and where StartServers, MaxRequestWorkers and ThreadsPerChild end up in the code — all checked with ps, mod_status and gdb.
-
Digging into Tomcat's Source 5: The Connector and the NIO Endpoint — Which Threads Take a Connection
We follow in the source how one line of server.xml, <Connector port="8080">, becomes three objects — Connector, Http11NioProtocol and NioEndpoint — and how the Acceptor, the Poller and the worker threads share the work of a single connection inside them. With jstack and a raw-socket client we check how a keep-alive connection lets go of its thread between requests, how Tomcat's thread pool (TaskQueue) behaves differently from the JDK's, and where maxConnections, acceptCount and maxThreads take effect. We also compare with 11.0.x and see that the NIO2 connector is gone from the main branch.
-
Digging into Tomcat's Source 12: Async Servlets and Non-Blocking I/O — Letting Go of the Thread
A servlet that calls startAsync() returns without finishing its response, and the request thread goes back to the pool. We follow, in Tomcat's source and with examples that print thread names, the AsyncStateMachine that holds the request in the meantime, the path complete() and dispatch() take back to a container thread, the timeout check that runs once a second, and how a ReadListener works together with the Poller.