• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Resolving thread exhaustion and asyncio event loop delays under heavy IO loads Under Peak Event Traffic on DigitalOcean

Resolving thread exhaustion and asyncio event loop delays under heavy IO loads Under Peak Event Traffic on DigitalOcean

Diagnosing Thread Exhaustion with `strace` and `ps`

When your application, particularly one heavily reliant on I/O operations and potentially using threads (e.g., Python with `threading` or C++ with `pthread`), starts exhibiting unresponsiveness under peak load on DigitalOcean, thread exhaustion is a prime suspect. This often manifests as increased latency, dropped requests, and a general system slowdown. The first step is to confirm if threads are indeed the bottleneck.

We’ll use a combination of `ps` and `strace` to get a granular view of the running processes and their system call activity. Assume your application runs as a user `appuser` and its main process ID (PID) can be found. If you have multiple worker processes, you’ll need to repeat this for each relevant PID.

Identifying Process Threads and Their States

The `ps` command, with the right flags, can reveal the number of threads associated with a process and their current state. This is crucial for understanding if the process is actively creating or managing a large number of threads.

Execute the following command on your DigitalOcean droplet. Replace `[APP_PID]` with the actual Process ID of your application.

ps -T -p [APP_PID] -o tid,pid,stat,comm | grep -v 'TID'

Explanation of flags:

  • -T: Show threads, possibly with SPID column.
  • -p [APP_PID]: Specify the process ID to monitor.
  • -o tid,pid,stat,comm: Define the output format: Thread ID (TID), Process ID (PID), process state (STAT), and command name (COMM).
  • grep -v 'TID': Excludes the header line from the output.

Observe the STAT column. Common states include:

  • R: Running or runnable (on run queue).
  • S: Interruptible sleep (waiting for an event to complete).
  • D: Uninterruptible sleep (usually I/O-bound, waiting for disk or network).
  • Z: Zombie (terminated but not reaped by its parent).

A large number of threads in the R state indicates high CPU contention, while many threads in the D state strongly suggest I/O blocking. If you see a rapidly increasing number of threads, especially in the R or D states, thread exhaustion is highly probable.

Deep Dive with `strace` for I/O Bottlenecks

To pinpoint the exact I/O operations causing threads to block, `strace` is invaluable. It intercepts and records system calls made by a process and signals received by it. Attaching `strace` to a running process can provide real-time insights into what system calls are consuming time or causing threads to enter uninterruptible sleep (D state).

First, ensure `strace` is installed:

sudo apt update && sudo apt install -y strace

Then, attach `strace` to your application’s main process. It’s crucial to capture system calls related to I/O. We’ll focus on `read`, `write`, `poll`, `select`, `epoll_wait`, and network-related calls.

sudo strace -p [APP_PID] -s 1024 -e trace=io,network,process,signal -f -o /tmp/strace_app.log

Explanation of flags:

  • -p [APP_PID]: Attach to the specified process ID.
  • -s 1024: Set the string size to 1024 bytes to see more data in read/write calls.
  • -e trace=io,network,process,signal: Filter system calls to only include I/O operations (like read, write, open), network operations (like sendto, recvfrom, connect), process management (like fork, clone), and signals.
  • -f: Trace child processes and threads. This is vital for multi-threaded applications.
  • -o /tmp/strace_app.log: Write the output to a file. This prevents flooding your terminal and allows for later analysis.

While `strace` is running, observe the `/tmp/strace_app.log` file. Look for patterns:

  • Repeated calls to read() or write() on the same file descriptor, especially if they are taking a long time or returning errors (like EAGAIN or EWOULDBLOCK).
  • Frequent calls to poll(), select(), or epoll_wait() that are returning with no events ready, indicating the application is polling for I/O that isn’t arriving quickly enough.
  • High frequency of network-related system calls (e.g., sendmsg, recvmsg) with large data transfers.
  • System calls related to thread creation (clone()) if the number of threads is indeed growing rapidly.

The output might look something like this, showing a `read` call blocking:

[pid 12345] read(3, "...", 1024) = -1 EAGAIN (Resource temporarily unavailable)

Or a `poll` call that returns no events:

[pid 12345] poll([{fd=4, events=POLLIN}], 1, 5000) = 0 (Timeout)

This information is critical for understanding where the application is spending its time waiting and which I/O operations are becoming bottlenecks.

Addressing Asyncio Event Loop Delays Under Load

For applications built with Python’s `asyncio`, thread exhaustion can still be a symptom, but the primary concern under heavy I/O load is often the event loop itself becoming blocked or overwhelmed. An `asyncio` event loop is designed to be single-threaded and cooperative. If any part of the code within the event loop performs a blocking operation (e.g., a synchronous I/O call, a long-running CPU-bound task without offloading), it will halt the entire loop, preventing other coroutines from making progress.

Identifying Event Loop Blocking with `uvloop` and Profiling

The `uvloop` library, a drop-in replacement for the default `asyncio` event loop, is significantly faster and provides better diagnostics. It can help identify blocking calls more readily.

First, install `uvloop`:

pip install uvloop

Then, configure your application to use `uvloop`:

import asyncio
import uvloop

asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

# Your asyncio application code here...

When `uvloop` is active, it can often surface blocking calls more aggressively. You might see exceptions or performance degradation that points to a specific coroutine hogging the event loop.

For more detailed profiling, Python’s built-in `cProfile` module is essential. You can use it to record function call times and identify which parts of your asynchronous code are taking the longest.

python -m cProfile -o /tmp/async_profile.prof your_async_app.py

After running this under load, analyze the profile data. Tools like `snakeviz` can visualize the profile output, making it easier to spot bottlenecks.

pip install snakeviz
snakeviz /tmp/async_profile.prof

Look for coroutines that are consuming a disproportionate amount of time, especially those that should be yielding control back to the event loop but are instead performing synchronous operations.

Strategies for Mitigating Asyncio Event Loop Blocking

The core principle is to keep the event loop free to handle I/O. Any operation that might take a significant amount of time (more than a few milliseconds) should be offloaded.

1. Offloading Blocking I/O to a Thread Pool

For synchronous I/O libraries or operations that don’t have native `asyncio` support, use `loop.run_in_executor()` to run them in a separate thread pool. This prevents them from blocking the main event loop.

import asyncio
import time
import requests # Example of a synchronous library

async def fetch_url_sync(url):
    loop = asyncio.get_running_loop()
    # Run the blocking requests.get call in the default executor (thread pool)
    response = await loop.run_in_executor(
        None,  # Use the default executor
        requests.get,
        url
    )
    return response.text

async def main():
    # ... other async tasks ...
    data = await fetch_url_sync("http://example.com")
    print(f"Fetched data length: {len(data)}")
    # ...

The `None` argument to `run_in_executor` uses the default `ThreadPoolExecutor` configured for the event loop. You can also pass a custom `concurrent.futures.Executor` instance if you need finer control over the thread pool size.

2. Offloading CPU-Bound Tasks

Similarly, CPU-intensive computations should not run directly within a coroutine. Use `run_in_executor` with a `ProcessPoolExecutor` for truly CPU-bound tasks to leverage multiple CPU cores.

import asyncio
import time
from concurrent.futures import ProcessPoolExecutor

def cpu_intensive_task(n):
    # Simulate a heavy computation
    result = 0
    for i in range(n):
        result += i * i
    return result

async def run_cpu_task_async(n):
    loop = asyncio.get_running_loop()
    # Use a ProcessPoolExecutor for CPU-bound tasks
    with ProcessPoolExecutor() as pool:
        result = await loop.run_in_executor(pool, cpu_intensive_task, n)
    return result

async def main():
    print("Starting CPU-bound task...")
    result = await run_cpu_task_async(10_000_000)
    print(f"CPU task result: {result}")

3. Optimizing Asynchronous I/O Operations

Ensure that all I/O operations are using asynchronous libraries. For example, instead of `requests`, use `aiohttp`. Instead of standard file I/O, use `aiofiles`.

import asyncio
import aiohttp
import aiofiles

async def fetch_url_async(session, url):
    async with session.get(url) as response:
        return await response.text()

async def read_file_async(filepath):
    async with aiofiles.open(filepath, mode='r') as f:
        contents = await f.read()
    return contents

async def main():
    async with aiohttp.ClientSession() as session:
        html = await fetch_url_async(session, "http://example.com")
        print(f"Fetched HTML length: {len(html)}")

    file_content = await read_file_async("my_data.txt")
    print(f"File content length: {len(file_content)}")

Monitoring `asyncio` Performance

Beyond profiling, continuous monitoring is key. Tools like Prometheus with custom exporters or libraries that expose `asyncio` metrics can provide real-time insights into event loop lag and task execution times. Look for metrics like:

  • Event Loop Lag/Latency: The time it takes for the event loop to process a tick. High lag indicates blocking.
  • Task Execution Time: Average and P99 execution times for critical asynchronous tasks.
  • Number of Active Tasks: Monitor the growth of active tasks to detect potential resource leaks.
  • I/O Operations per Second: Track the throughput of your asynchronous I/O.

DigitalOcean Specific Considerations

While the principles are universal, DigitalOcean’s infrastructure can influence performance. Ensure your Droplet size is adequate for your peak load. Insufficient CPU or RAM will exacerbate any application-level bottlenecks.

Network Throughput and Latency

DigitalOcean’s network performance is generally good, but under extreme load, you might hit bandwidth limits or experience increased latency, especially if your application is communicating with external services or databases. Use tools like `iperf3` to test network throughput between your Droplets or to external endpoints.

# On server A (client)
iperf3 -c [IP_ADDRESS_OF_SERVER_B]

# On server B (server)
iperf3 -s

If network I/O is consistently slow or unreliable, consider:

  • Increasing Droplet size for better network interfaces.
  • Using DigitalOcean’s Private Networking for inter-Droplet communication if applicable.
  • Optimizing application-level network protocols (e.g., using persistent connections, reducing chattiness).
  • Distributing load across multiple Droplets and regions.

Disk I/O Performance

DigitalOcean’s standard SSDs are performant, but heavy synchronous disk I/O from your application (e.g., logging, file processing) can still lead to threads entering uninterruptible sleep (D state). If `strace` points to disk I/O as a bottleneck, consider:

  • Moving to Droplets with NVMe SSDs for higher IOPS.
  • Optimizing application logic to reduce unnecessary disk writes.
  • Using asynchronous file I/O libraries where possible.
  • Offloading disk-intensive tasks to dedicated storage solutions if necessary.

You can benchmark disk I/O using tools like `fio`:

# Example: Sequential write test
fio --name=seqwrite --ioengine=libaio --direct=1 --rw=write --bs=1M --size=1G --numjobs=4 --runtime=60 --group_reporting

Conclusion

Resolving thread exhaustion and event loop delays under heavy I/O loads requires a systematic approach. Start with process-level diagnostics using `ps` and `strace` to identify blocking operations. For `asyncio` applications, focus on profiling and ensuring all I/O and CPU-bound tasks are properly offloaded. Always consider the underlying infrastructure, including network and disk performance on your DigitalOcean Droplets. By combining these diagnostic techniques and architectural patterns, you can build more resilient and performant applications capable of handling peak event traffic.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Top 100 Developer Tooling and Productivity SaaS Ideas to Launch in 2026 to Boost Organic Search Growth by 200%
  • Top 100 Developer-Centric Code Snippet Managers and Customization Plugins to Double User Engagement and Session Duration
  • Top 5 API Monetization Frameworks and Gateway Strategies for Developers to Minimize Server Costs and Load Overhead
  • Top 50 Automated PDF & Document Generation Tool Ideas for Developers to Minimize Server Costs and Load Overhead
  • Top 50 Premium Newsletter and Subscription Business Models for Devs for High-Traffic Technical Portals

Categories

  • apache (1)
  • Business & Monetization (386)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (497)
  • DevOps (7)
  • DevOps & Cloud Scaling (921)
  • Django (1)
  • Migration & Architecture (85)
  • MySQL (1)
  • Performance & Optimization (642)
  • PHP (5)
  • Plugins & Themes (114)
  • Security & Compliance (524)
  • SEO & Growth (443)
  • Server (23)
  • Ubuntu (9)
  • WordPress (22)
  • WordPress Plugin Development (7)
  • WordPress Theme Development (60)

Recent Posts

  • Top 100 Developer Tooling and Productivity SaaS Ideas to Launch in 2026 to Boost Organic Search Growth by 200%
  • Top 100 Developer-Centric Code Snippet Managers and Customization Plugins to Double User Engagement and Session Duration
  • Top 5 API Monetization Frameworks and Gateway Strategies for Developers to Minimize Server Costs and Load Overhead
  • Top 50 Automated PDF & Document Generation Tool Ideas for Developers to Minimize Server Costs and Load Overhead
  • Top 50 Premium Newsletter and Subscription Business Models for Devs for High-Traffic Technical Portals
  • Top 100 SEO and Schema Markup Plugins for Headless Decoupled Sites for Independent Web Developers and Indie Hackers

Top Categories

  • DevOps & Cloud Scaling (921)
  • Performance & Optimization (642)
  • Security & Compliance (524)
  • Debugging & Troubleshooting (497)
  • SEO & Growth (443)
  • Business & Monetization (386)

Our Products

  • School Management & Student Administration System
  • Integrated Hospital & Clinic Management System
  • Real Estate Directory & Agent Portal
  • Restaurant POS & Table Booking System
  • Retail Inventory POS & Billing System
  • Pharmacy Inventory & Clinic Billing System

Our Services

  • Vibe Engineering & AI Code Auditing Services
  • Prompt Engineering & "Vibe Coding" Workflow Consulting
  • AI-Augmented "Vibe Coding" & Rapid MVP Development
  • Figma to Shopify Liquid Theme Customization
  • Figma to WooCommerce Frontend Development
  • Figma to Magento 2 Theme Development

Copyright © 2026 · Vinay Vengala