• 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 » Step-by-Step: Diagnosing thread exhaustion and asyncio event loop delays under heavy IO loads on OVH Servers

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

Identifying Thread Exhaustion with `strace` and `perf`

When dealing with high I/O loads on OVH servers, particularly those running applications that rely on multithreading or asynchronous I/O, thread exhaustion can manifest as severe performance degradation and unresponsiveness. A common symptom is a sudden spike in latency or complete application freezes. We’ll start by using `strace` to observe system calls and `perf` for deeper performance profiling.

First, let’s identify the processes that are experiencing high I/O. We can use `iotop` for a real-time view, but for deeper analysis, we need to attach `strace` to a suspect process. Find the PID of your application (e.g., a Python web server or a C++ data processing service) using `ps aux | grep your_app_name`.

Using `strace` for System Call Analysis

Attach `strace` to the process to see what system calls it’s making. This can reveal if the application is stuck in a loop of I/O operations or waiting excessively on resources. Use the `-p` flag for the PID and `-c` to get a summary of system calls.

sudo strace -p  -c -f

The `-f` flag is crucial as it traces child processes as well, which is common in multithreaded applications. Analyze the output for system calls like `read`, `write`, `poll`, `select`, `epoll_wait`, and `futex`. A disproportionately high number of calls to these, especially if they are taking a long time (indicated by the average time column), points towards I/O bottlenecks. Pay close attention to `futex` calls, as they are often related to thread synchronization and can indicate contention.

Leveraging `perf` for Thread and Event Loop Profiling

For a more granular view of what the CPU is spending its time on, `perf` is an invaluable tool. It can help identify if threads are spending excessive time waiting for locks, sleeping, or blocked on I/O. We can sample the running process to understand its behavior.

sudo perf top -p 

This command will show a real-time view of functions consuming CPU time. Look for functions related to I/O operations, thread synchronization primitives (like mutexes or semaphores), or event loop mechanisms (e.g., `select`, `epoll_wait` in C/C++, or internal Python event loop functions). If you see a lot of time spent in functions like `futex_wait` or `__lll_lock_wait`, it strongly suggests thread contention.

sudo perf record -p  -g -- sleep 10

The command above records performance data for 10 seconds with call graphs enabled. After recording, you can analyze the data:

sudo perf report

Navigate through the `perf report` interface. Look for functions that are frequently called or take a significant amount of time. If you see a high percentage of time spent in I/O-related kernel functions or in synchronization primitives, it confirms the issue. The call graph (`-g`) is essential for understanding the context – which part of your application is triggering these slow operations.

Diagnosing `asyncio` Event Loop Delays

For Python applications using `asyncio`, event loop delays under heavy I/O are a classic problem. The event loop can become blocked if a synchronous, long-running operation is executed within an `async` function, preventing other coroutines from running. We can use Python’s built-in profiling tools and `strace` to pinpoint these issues.

Python’s `asyncio` Debug Mode and Profiling

Enable `asyncio`’s debug mode. This adds overhead but provides valuable warnings about slow callbacks and other potential issues.

import asyncio

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

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main())
    finally:
        loop.close()
        # Enable debug mode for the loop
        loop.set_debug(True)

When `asyncio` debug mode is enabled, it will log warnings if callbacks take longer than a certain threshold (default is 0.1 seconds). This is a strong indicator of a blocking operation. Additionally, you can use Python’s `cProfile` module to profile your `asyncio` application. While `cProfile` isn’t `asyncio`-aware by default, it can still reveal which functions are consuming the most time.

python -m cProfile -o profile.prof your_asyncio_app.py

Then, analyze the profile data:

import pstats
p = pstats.Stats('profile.prof')
p.sort_stats('cumulative').print_stats(20) # Print top 20 cumulative time consumers
p.sort_stats('tottime').print_stats(20)    # Print top 20 total time consumers

Look for synchronous I/O calls (e.g., standard `open`, `read`, `write` on files, or blocking network calls) or CPU-bound tasks that are not properly offloaded to a thread pool or process pool.

Correlating `strace` with `asyncio` Behavior

When `asyncio` debug mode or `cProfile` points to a specific operation, use `strace` on the Python interpreter’s PID to confirm if that operation is indeed making blocking system calls. For example, if you suspect a file read operation is blocking the event loop:

sudo strace -p  -e trace=read,write,open,close,futex -s 1024

The `-e trace=` option filters the output to only show relevant system calls. The `-s 1024` increases the string size shown, which can be helpful for inspecting file paths or buffer contents. If you see a `read` or `write` call that takes an unusually long time, and it’s not part of an `aio` operation (like `aio.open` or `asyncio.open_connection`), it’s likely the culprit. Similarly, excessive `futex` calls might indicate that the underlying threading model used by Python for I/O operations is experiencing contention.

Thread Pool Exhaustion in `asyncio`

Many `asyncio` applications use `loop.run_in_executor()` to run blocking code in a separate thread pool. If this thread pool becomes exhausted, new blocking tasks will queue up, leading to delays. The default thread pool size might be insufficient under heavy load.

import asyncio
import concurrent.futures

async def main():
    loop = asyncio.get_running_loop()
    # Use a ThreadPoolExecutor with a larger max_workers
    with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
        # Submit blocking tasks
        future1 = loop.run_in_executor(executor, blocking_io_task, "data1")
        future2 = loop.run_in_executor(executor, blocking_io_task, "data2")
        # ... more tasks
        await asyncio.gather(future1, future2)

def blocking_io_task(data):
    # Simulate a blocking I/O operation
    import time
    time.sleep(5)
    print(f"Processed: {data}")

if __name__ == "__main__":
    asyncio.run(main())

To diagnose thread pool exhaustion, you can:

  • Monitor the number of active threads in your application’s process using `htop` or `top` (look for a significant increase in threads).
  • Use `perf` to see if threads are spending time waiting in queues for the thread pool.
  • Add logging within your `blocking_io_task` to measure execution times and queueing delays.

OVH Server Specific Considerations

OVH servers, especially dedicated ones, can have varying network configurations and hardware. Ensure that your application’s I/O patterns are not hitting any specific network interface limits or kernel tuning parameters that might be misconfigured. Tools like `netstat -s` and `ss -s` can provide kernel-level network statistics that might indicate packet drops or retransmissions, which can exacerbate I/O delays.

netstat -s | grep -E 'retrans|drops|errors'
ss -s

If you are using specific OVH services like managed databases or load balancers, their performance metrics should also be scrutinized. A slow database query or an overloaded load balancer can appear as application-level I/O issues.

Preventative Measures and Best Practices

To prevent thread exhaustion and event loop delays:

  • Asynchronous I/O: Use native asynchronous libraries (e.g., `aiohttp`, `asyncpg`, `aiofiles`) for I/O operations whenever possible.
  • Thread Pool Sizing: Carefully tune the size of your thread pools for `run_in_executor` based on your workload and the nature of the blocking tasks.
  • Resource Monitoring: Implement robust monitoring for CPU, memory, network I/O, and thread counts.
  • Code Profiling: Regularly profile your application, especially under load, to catch performance regressions early.
  • Kernel Tuning: For very high-throughput I/O, consider tuning kernel parameters related to network buffers and file descriptor limits (e.g., `/etc/sysctl.conf`, `/etc/security/limits.conf`).

By systematically applying these diagnostic techniques, you can effectively pinpoint the root cause of thread exhaustion and `asyncio` event loop delays on your OVH servers, leading to more stable and performant applications.

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

  • Leveraging PHP 8.3 JIT and Vectorization for Extreme Performance in Laravel Applications
  • Leveraging PHP 9’s JIT Compiler and Concurrent Execution for High-Performance Laravel Microservices
  • Leveraging PHP 8.3 JIT and Vectorization for High-Throughput Microservices in a Laravel Ecosystem
  • Leveraging Laravel Octane and Docker Swarm for High-Performance, Scalable WordPress Headless Deployments
  • Migrating Legacy WordPress to Headless with Laravel: A Performance and Security Deep Dive

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (664)
  • Desktop Applications (14)
  • DevOps (11)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (6)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • PHP (18)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (10)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (650)
  • SEO & Growth (492)
  • Server (118)
  • Softwares (1)
  • Ubuntu (9)
  • Uncategorized (23)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (26)
  • WordPress Plugin Development (728)
  • WordPress Theme Development (357)

Recent Posts

  • Leveraging PHP 8.3 JIT and Vectorization for Extreme Performance in Laravel Applications
  • Leveraging PHP 9's JIT Compiler and Concurrent Execution for High-Performance Laravel Microservices
  • Leveraging PHP 8.3 JIT and Vectorization for High-Throughput Microservices in a Laravel Ecosystem

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (728)
  • Debugging & Troubleshooting (664)
  • Security & Compliance (650)
  • SEO & Growth (492)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala