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

Vengala Vinay

Having 9+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Step-by-Step: Diagnosing thread exhaustion and asyncio event loop delays under heavy IO loads on AWS Servers

Step-by-Step: Diagnosing thread exhaustion and asyncio event loop delays under heavy IO loads on AWS Servers

Identifying Thread Exhaustion with `top` and `htop`

Thread exhaustion on AWS EC2 instances, particularly under heavy I/O loads, often manifests as unresponsiveness, increased latency, and application errors. A common culprit is a thread pool that cannot keep up with incoming requests, leading to new requests being queued indefinitely or dropped. We’ll start by using standard Linux utilities to get a high-level view of system resource utilization, focusing on threads.

The `top` command provides a real-time view of running processes. To specifically observe thread activity, we can use the `H` flag. This will display each thread as a separate entry, allowing us to see which processes are spawning an excessive number of threads and how much CPU each thread is consuming.

Using `top` to Monitor Threads

Execute `top` and then press `H` to toggle thread display. Look for processes with a very high number of threads (PID column will be the same for threads belonging to the same process). Pay close attention to the `%CPU` column for individual threads. A single thread consuming a disproportionate amount of CPU can indicate a bottleneck or a runaway process.

top -H

While `top` is useful, `htop` offers a more interactive and visually intuitive experience. It color-codes output and allows for easier sorting and filtering. If `htop` is not installed, you can typically install it via your distribution’s package manager (e.g., `sudo apt-get install htop` or `sudo yum install htop`).

Leveraging `htop` for Detailed Thread Analysis

Once `htop` is running, press `F2` for Setup, navigate to “Display options,” and ensure “Show custom thread names” and “Hide userland thread names” are unchecked. Then, navigate to “Columns” and add “TID” (Thread ID) and “NLWP” (Number of Light Weight Processes, essentially threads) if they aren’t already present. Press `F1` to go back and `F10` to Save and Exit. You can then sort by the “NLWP” column to quickly identify processes with many threads.

htop

When observing `htop`, look for processes with a high NLWP count. If a specific application process (e.g., your Python web server, Java application, or Node.js service) is consistently showing a large number of threads, it’s a strong indicator of potential thread exhaustion or inefficient thread management. Also, monitor the CPU usage per thread. If many threads are active but none are making significant progress, it points to an I/O bound problem where threads are waiting.

Diagnosing `asyncio` Event Loop Delays Under I/O Load

For applications built with Python’s `asyncio`, thread exhaustion isn’t the primary concern in the same way as traditional multithreaded applications. Instead, the bottleneck often lies within the single-threaded event loop becoming blocked or overwhelmed by I/O operations. This leads to increased latency and unresponsive applications, even if CPU usage appears low.

Profiling `asyncio` Event Loop Performance

Python’s built-in `asyncio` debugging features and external profiling tools are essential here. We can use `asyncio`’s debug mode to log slow callbacks and other potential issues. For more in-depth analysis, tools like `uvloop` (a drop-in replacement for the default event loop) can offer performance insights, and libraries like `aiomonitor` provide real-time introspection.

First, enable `asyncio` debug mode. This adds overhead, so it’s typically used in development or staging, but can be invaluable for diagnosing production issues. It will log warnings for callbacks that take longer than a specified threshold (default is 0.1 seconds).

import asyncio

async def main():
    # ... your asyncio code ...
    pass

if __name__ == "__main__":
    # Enable asyncio debug mode
    asyncio.run(main(), debug=True)

When `debug=True` is set, `asyncio` will print warnings to stderr for operations that take too long. Look for messages like:

Executing  wait_for=> took 0.521 seconds

This indicates that `my_slow_io_operation` is taking significantly longer than expected, likely due to blocking I/O or an inefficient asynchronous operation. The key is to identify which coroutines are causing these delays.

Using `aiomonitor` for Real-time Event Loop Inspection

`aiomonitor` is a powerful library that allows you to remotely inspect and debug running `asyncio` applications. It provides a REPL-like interface to inspect the event loop, tasks, and coroutines. This is incredibly useful for understanding what the event loop is doing *right now*.

Install `aiomonitor`:

pip install aiomonitor

Integrate `aiomonitor` into your application. You typically start it in a separate thread or task:

import asyncio
import aiomonitor

async def my_long_running_task():
    print("Starting long task...")
    await asyncio.sleep(10) # Simulate I/O
    print("Long task finished.")

async def main():
    # Start aiomonitor on localhost:50101
    monitor = aiomonitor.Monitor('127.0.0.1', 50101)
    await monitor.start()
    print("Aiomonitor started on 127.0.0.1:50101")

    # Schedule your application tasks
    asyncio.create_task(my_long_running_task())
    # ... other tasks ...

    # Keep the event loop running
    await asyncio.Future()

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("Shutting down.")



Once your application is running with `aiomonitor` enabled, you can connect to it from another terminal:

telnet 127.0.0.1 50101

Inside the `aiomonitor` REPL, you can use commands like:

  • tasks: List all running tasks.
  • tasks --all: List all tasks, including done ones.
  • tasks --pending: List only pending tasks.
  • tasks --running: List only running tasks.
  • tasks --coro : Show details of a specific coroutine.
  • loop: Inspect the event loop.

By observing the `tasks` output during periods of high load, you can identify which coroutines are stuck in `await` calls for extended periods. This often points to blocking I/O operations that are not properly handled asynchronously or are themselves slow.

AWS Specifics: Monitoring and Tuning

AWS provides several services that are crucial for diagnosing and mitigating these issues. Understanding your EC2 instance's network and disk I/O performance is paramount.

Utilizing CloudWatch Metrics

Amazon CloudWatch is your first line of defense for monitoring EC2 instances. Key metrics to watch include:

  • CPU Utilization: High CPU can indicate a CPU-bound problem, but also a symptom of threads spinning or an event loop struggling.
  • Network In/Out: Spikes or sustained high usage can point to network I/O bottlenecks.
  • Disk Read/Write Operations: High IOPS or throughput can indicate disk I/O is the limiting factor.
  • Disk Read/Write Bytes: Similar to operations, but measures data volume.
  • Network Packets In/Out: High packet rates can indicate chatty protocols or inefficient network usage.
  • Status Checks: Both instance and system status checks failing are critical indicators of underlying hardware or network issues.

For `asyncio` applications, pay close attention to the relationship between network/disk I/O metrics and application latency. If I/O metrics are high and application response times are increasing, it's a strong correlation.

Instance Type and EBS Volume Optimization

The choice of EC2 instance type and Elastic Block Store (EBS) volume configuration significantly impacts I/O performance. For I/O-intensive workloads:

  • Instance Types: Consider instance types optimized for I/O, such as those in the `i` (NVMe SSD instance storage) or `d` (dense storage) families. For network-intensive workloads, look at `c` (compute optimized) or `r` (memory optimized) instances with enhanced networking capabilities.
  • EBS Volumes:
    • `gp3` volumes: Offer baseline performance and the ability to provision IOPS and throughput independently, making them flexible and cost-effective for many workloads.
    • `io1`/`io2` volumes: Provide the highest performance and durability for I/O-intensive applications that require consistent, low-latency access. Ensure you provision sufficient IOPS and throughput for your workload.
    • Instance Store Volumes: If your application can tolerate ephemeral storage, instance store volumes (especially NVMe-based ones on `i` series instances) offer extremely high performance.

When diagnosing, check your EBS volume's CloudWatch metrics for Queue Length. A consistently high queue length indicates that the volume cannot keep up with the demand, and you may need to provision more IOPS/throughput or switch to a higher-performance volume type.

Network Configuration and Tuning

For applications heavily reliant on network I/O, ensure your EC2 instance is configured for enhanced networking. Most modern instance types support this, providing higher bandwidth, lower latency, and reduced CPU utilization for network processing. Verify this in your instance's details in the AWS console.

If you're seeing high network packet counts or latency, consider:

  • TCP Tuning: While often handled by the OS, in extreme cases, kernel parameters related to TCP buffers (`net.core.rmem_max`, `net.core.wmem_max`, `net.ipv4.tcp_rmem`, `net.ipv4.tcp_wmem`) might need adjustment. This is an advanced topic and should be approached with caution.
  • Application-level batching: If your application makes many small, independent network requests, consider batching them to reduce overhead.
  • Connection pooling: Ensure your application uses connection pooling effectively for databases and external services to avoid the overhead of establishing new connections repeatedly.

Advanced Debugging: Tracing and System Calls

When standard tools and metrics aren't enough, diving deeper into system calls and kernel-level activity can reveal the root cause of I/O bottlenecks.

Using `strace` for System Call Tracing

`strace` is a powerful diagnostic tool that intercepts and records system calls made by a process and signals received by a process. It can show exactly what I/O operations your application is attempting and how the kernel is responding.

To trace a running process (e.g., with PID 12345):

sudo strace -p 12345 -s 1024 -f -tt -T -o /tmp/process_12345.strace

Explanation of flags:

  • -p 12345: Attach to process ID 12345.
  • -s 1024: Print up to 1024 bytes of string arguments.
  • -f: Trace child processes (threads).
  • -tt: Print timestamps with microsecond precision.
  • -T: Show the time spent in each system call.
  • -o /tmp/process_12345.strace: Write output to a file.

Analyze the output file (`/tmp/process_12345.strace`). Look for system calls that are taking a long time (`-T` flag) or are being called excessively. Common I/O-related system calls include:

  • read(), write(), readv(), writev(): For general file and network I/O.
  • send(), recv(), sendto(), recvfrom(): For network socket operations.
  • poll(), select(), epoll_wait(): For waiting on I/O events. If these are consistently blocking for long durations, it indicates the event loop (or underlying I/O mechanisms) are waiting.
  • fsync(), fdatasync(): For synchronizing data to disk.

If you see many `read()` or `write()` calls to disk devices (e.g., `/dev/nvme0n1p1`) that are taking a long time, it confirms disk I/O is a bottleneck. If network calls like `recv()` are consistently slow, it points to network latency or a slow upstream service.

Using `perf` for Performance Profiling

The `perf` tool, part of the Linux kernel's performance analysis suite, provides deeper insights into CPU usage, kernel events, and hardware performance counters. It can help identify hot spots in your code or kernel interactions.

To profile a process for 30 seconds, focusing on CPU cycles:

sudo perf record -p 12345 -g -- sleep 30
sudo perf report

The `-g` flag enables call graph recording, which is crucial for understanding the context of performance issues. `perf report` provides an interactive TUI to explore the profiling data. Look for functions or system calls that consume a high percentage of CPU time.

For I/O-bound issues, `perf` can also monitor specific kernel events. For example, to see how much time is spent waiting for I/O:

sudo perf record -e 'block:*' -p 12345 -g -- sleep 30
sudo perf report

This command records events related to block device I/O. Analyzing the `perf report` output for these events can pinpoint specific disk I/O operations that are causing delays.

Conclusion and Mitigation Strategies

Diagnosing thread exhaustion and `asyncio` event loop delays under heavy I/O loads requires a multi-faceted approach. Start with high-level system monitoring (`top`, `htop`, CloudWatch), then drill down into application-specific tools (`asyncio` debug mode, `aiomonitor`), and finally, use low-level tracing (`strace`, `perf`) when necessary.

Mitigation strategies often involve:

  • Optimizing I/O Operations: Ensure asynchronous I/O is used correctly in `asyncio` applications. For traditional threads, use non-blocking I/O or thread pools sized appropriately.
  • Resource Provisioning: Scale up EC2 instance types, provision higher-performance EBS volumes, or optimize network bandwidth.
  • Code Refactoring: Identify and optimize slow coroutines or blocking calls. Implement proper error handling and timeouts.
  • Concurrency Model Adjustment: For `asyncio`, consider offloading CPU-bound tasks to a separate thread pool using `loop.run_in_executor()`. For threaded applications, tune thread pool sizes and consider asynchronous frameworks if applicable.
  • Caching: Implement caching layers (e.g., Redis, Memcached) to reduce load on databases and external services.
  • Load Balancing: Distribute traffic across multiple instances to prevent any single instance from becoming a bottleneck.

By systematically applying these diagnostic techniques and understanding the interplay between application code, the operating system, and AWS infrastructure, you can effectively resolve performance issues related to heavy I/O loads.

Primary Sidebar

A little about the Author

Having 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Step-by-Step: Diagnosing indexing lock conflicts and high CPU during bulk stock updates on DigitalOcean Servers
  • How to Debug and Fix memory leaks and socket exhaustion in daemon processes in Modern C++ Applications
  • Infrastructure as Code: Provisioning Secure PHP Clusters on DigitalOcean Using Terraform
  • Fixing Slow Largest Contentful Paint (LCP) caused by unoptimized database queries in Legacy Laravel Codebases Without Breaking API Contracts
  • An Auditor’s Checklist for Securing Laravel Backends on Google Cloud

Copyright © 2026 · Vinay Vengala