NeuroAgent

PHP HTTP Response with Background Processing: Complete Guide

Learn how to send immediate HTTP responses while continuing backend processing in PHP. Explore pcntl_fork vs fastcgi_finish_request with best practices.

Question

PHP HTTP server processing to immediately send HTTP response, while continue backend processing in separate process

I’m implementing a PHP application on server A that receives a request from a client (browser), which will lead to a HTTP request to be sent to another server B. The client request is then answered (HTTP response sent).

Some time later, server A receives a call back HTTP request from the server B. Server B expects an (immediate) HTTP response with status code 200. Then, this part ends, while server A has to finalize the data received from server B.

Putting the data on some kind of queue, and periodically run a cron job to look at the queue and work on the data is not suitable here, mainly because the data a) shall be worked on immediately, and b) the events rarely occur (the cron job would mostly run for nothing).

So, I was thinking of using fork() to create a child process. The parent will then respond to server B, while the child process would work on the data.

So here is the code without processing details:

php
<?php

$pid = pcntl_fork();

switch ($pid) {
    case -1:
        // unexpected error handled here.
        exit();
        break;

    case 0:
        // child will process the data in its own process
        //
        // Not sure whether we still have an open connection to the "caller"
        //
        
        exit();
        break;

    default :
        // parent responds to server, then terminates, closing the connection
        ignore_user_abort(TRUE);
        ob_start();
        header('Connection: close');
        header('Content-Length: 0');
        http_response_code(200);
        ob_end_flush();
        flush();

        // check if fastcgi_finish_request is callable
        if (is_callable('fastcgi_finish_request')) {
            fastcgi_finish_request();
        }

        exit(); // This should finally close the connection
        break;
}

?>

Does this look like a suitable solution?

I’m unclear about the state of the network connection in the child process. As per manual (as usual fork()) behaviour, the child inherits all open file descriptors, which includes network connections (if I’m not mistaken).

Possibly “pcntl_unshare()” shall be used to unlink network connections from parent? Unfortunately, the parameters like “CLONE_NEWNET” don’t seem to be described in the manual.

Any thoughts or advice are welcome

NeuroAgent

Your approach using pcntl_fork() is technically viable but has significant caveats regarding network connection handling. The child process inherits all file descriptors from the parent, including network connections, which can lead to unexpected behavior. A more robust solution exists using fastcgi_finish_request() which is specifically designed for this scenario in FastCGI environments.

Contents

Evaluating Your pcntl_fork Approach

Your implementation follows the standard pattern for process forking in PHP, but there are several important considerations:

Strengths:

  • The basic fork structure is correct, with proper error handling
  • You’re using ignore_user_abort(TRUE) to prevent the parent from being terminated
  • The Connection: close header and Content-Length: 0 approach is appropriate for immediate responses
  • You’re checking for fastcgi_finish_request() availability

Critical Issue:
The main problem is that both processes share the same network connection descriptors. As Hacking with PHP explains:

“Both parent and child inherit the same file descriptors which means that you can write to the same file from several processes if you want to. In practice this is undesirable as you will get intermingled lines of text from the different process”

This applies equally to network connections. When the parent process closes the connection to respond to server B, the child process may encounter connection errors or unexpected behavior.

Network Connection Challenges

The shared file descriptor behavior creates several potential issues:

Connection State Conflicts

  • When the parent flushes and closes the connection, the child may still have references to the same socket
  • This can lead to “connection reset by peer” errors in the child process
  • Both processes might attempt to use the same network resources simultaneously

Resource Cleanup Problems

  • File descriptors are reference-counted - both processes need to close them properly
  • Network buffers and connection state may become inconsistent between processes
  • Database connections (if any) become invalid in the child process

As noted in the Stack Overflow discussion:

“It is the way pcntl_fork was designed. Any extension that maintains it’s own file descriptors will then have corrupted descriptors because all children an parents share the same file descriptors.”

Solution: Explicit Connection Management

To mitigate these issues, you should:

php
<?php

$pid = pcntl_fork();

switch ($pid) {
    case -1:
        // unexpected error handled here.
        exit();
        break;

    case 0:
        // child will process the data in its own process
        // IMPORTANT: Close all shared network connections
        if (function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        }
        
        // Close any database connections
        // Close any other file descriptors that shouldn't be shared
        
        // Now process the data
        your_processing_function();
        
        exit();
        break;

    default :
        // parent responds to server, then terminates
        ignore_user_abort(TRUE);
        ob_start();
        header('Connection: close');
        header('Content-Length: 0');
        http_response_code(200);
        ob_end_flush();
        flush();

        // Use fastcgi_finish_request if available - this is cleaner
        if (is_callable('fastcgi_finish_request')) {
            fastcgi_finish_request();
        }

        exit();
        break;
}

?>

Better Alternative: fastcgi_finish_request

For your use case, fastcgi_finish_request() is actually the superior solution when running under PHP-FPM. According to the PHP manual:

This function finishes request for current process and makes possible to continue running code in background after client connection has been closed.

Advantages over pcntl_fork:

  • No process duplication overhead
  • No shared file descriptor issues
  • Cleaner resource management
  • Specifically designed for this exact use case
  • Works seamlessly with PHP-FPM’s process management

Implementation:

php
<?php

// Send immediate response to server B
ignore_user_abort(TRUE);
ob_start();
header('Connection: close');
header('Content-Length: 0');
http_response_code(200);
ob_end_flush();
flush();

// Detach from the client connection
if (function_exists('fastcgi_finish_request')) {
    fastcgi_finish_request();
}

// Continue processing in the same process
your_processing_function();

?>

When to Use Each Approach

Use Case fastcgi_finish_request pcntl_fork
Immediate HTTP response + background processing
Heavy computational tasks that benefit from separate processes
Multiple workers needed for parallel processing
Database connection isolation
Resource efficiency
Code simplicity

Connection Management Best Practices

For pcntl_fork Approach

  1. Close All Database Connections

    php
    // Before forking
    $db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
    
    // In child process
    if ($pid == 0) {
        $db = null; // Close connection
        // Reconnect if needed
        $db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
    }
    
  2. Handle File Descriptors

    php
    // In child process
    fclose(STDIN);
    fclose(STDOUT);
    fclose(STDERR);
    
    // Or redirect to /dev/null
    fclose(STDIN);
    fclose(STDOUT);
    fclose(STDERR);
    
  3. Signal Handling

    php
    // Set up signal handlers in child
    pcntl_signal(SIGTERM, "signal_handler");
    pcntl_signal(SIGINT, "signal_handler");
    
    function signal_handler($signo) {
        // Clean up and exit
        exit();
    }
    

For fastcgi_finish_request Approach

  1. Process Isolation

    • The same process continues execution after client disconnect
    • No need for special connection cleanup
    • All resources remain available
  2. Error Handling

    php
    try {
        fastcgi_finish_request();
        // Processing code here
    } catch (Exception $e) {
        // Error handling - won't affect the client
        error_log("Processing error: " . $e->getMessage());
    }
    

Implementation Recommendations

Recommended Solution: fastcgi_finish_request

php
<?php
/**
 * Handles callback from server B with immediate response
 * and background processing
 */

function handle_server_b_callback($data) {
    // Send immediate 200 response
    send_immediate_response();
    
    // Process data in background
    process_data_background($data);
}

function send_immediate_response() {
    ignore_user_abort(TRUE);
    ob_start();
    header('Connection: close');
    header('Content-Length: 0');
    http_response_code(200);
    ob_end_flush();
    flush();
    
    // Detach from client if possible
    if (function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request();
    }
}

function process_data_background($data) {
    // Your processing logic here
    // This runs after the client has disconnected
    
    // Example: Process and store the data
    $result = your_processing_function($data);
    
    // Log completion
    error_log("Background processing completed: " . json_encode($result));
    
    // Additional processing steps...
}

// Main execution
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $input = file_get_contents('php://input');
    $data = json_decode($input, true);
    
    handle_server_b_callback($data);
} else {
    http_response_code(405);
    echo 'Method not allowed';
}
?>

Alternative: pcntl_fork with Proper Cleanup

If you must use pcntl_fork (e.g., for heavy parallel processing):

php
<?php
function handle_server_b_callback($data) {
    $pid = pcntl_fork();
    
    if ($pid == -1) {
        // Fork failed
        send_error_response(500, "Internal server error");
        return;
    }
    
    if ($pid) {
        // Parent process
        send_immediate_response();
        // Parent can exit or continue with other tasks
        exit();
    } else {
        // Child process
        process_data_in_child($data);
        exit();
    }
}

function send_immediate_response() {
    ignore_user_abort(TRUE);
    ob_start();
    header('Connection: close');
    header('Content-Length: 0');
    http_response_code(200);
    ob_end_flush();
    flush();
    
    if (function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request();
    }
}

function process_data_in_child($data) {
    // Clean up shared resources
    cleanup_shared_resources();
    
    // Process the data
    $result = your_processing_function($data);
    
    // Log completion
    error_log("Child process completed: " . json_encode($result));
}

function cleanup_shared_resources() {
    // Close database connections
    // Close file handles
    // Clean up any shared state
    
    // Example for MySQL connections
    if (function_exists('mysql_close')) {
        mysql_close();
    }
    
    // Redirect stdio to /dev/null
    fclose(STDIN);
    fclose(STDOUT);
    fclose(STDERR);
    
    $nullFile = fopen('/dev/null', 'r');
    if ($nullFile) {
        STDIN = $nullFile;
    }
    
    $nullFile = fopen('/dev/null', 'w');
    if ($nullFile) {
        STDOUT = $nullFile;
        STDERR = $nullFile;
    }
}
?>

Conclusion

Your pcntl_fork() approach is functionally correct but introduces unnecessary complexity and potential connection management issues. The fastcgi_finish_request() function provides a cleaner, more efficient solution for your specific use case of sending an immediate HTTP response while continuing background processing.

Key recommendations:

  1. Use fastcgi_finish_request() when running under PHP-FPM - it’s specifically designed for this scenario
  2. Avoid pcntl_fork() unless you need true process isolation or parallel processing capabilities
  3. Implement proper resource cleanup if you must use forking
  4. Test thoroughly with connection scenarios to ensure reliability
  5. Consider error handling in background processes that won’t affect the immediate response

For your use case where you need to immediately respond to server B while continuing data processing, the fastcgi_finish_request() approach is superior in terms of simplicity, resource efficiency, and reliability.

Sources

  1. PHP: pcntl_fork - Manual
  2. Duplication of resources when forking – Hacking with PHP
  3. PHP: fastcgi_finish_request - Manual
  4. PHP fork process - parent reading variables updated by child - Stack Overflow
  5. php - pcntl_fork and the MySQL connection is gone - Stack Overflow
  6. Continue processing after closing connection - Stack Overflow
  7. Running Long-Running Tasks in PHP: Best Practices and Techniques