Skip to content

mod_http3/src/h3_server.c

Structs

Name
struct port_acquire_args

Functions

Name
void *APR_THREAD_FUNC port_acquire_thread_fn(apr_thread_t * , void * data)
h3_server_conf * find_h3_server(server_rec * s, server_rec ** out_server)
void h3_child_init(apr_pool_t * pchild, server_rec * s)
void h3_c1_child_stopping(apr_pool_t * pool, int graceful)

Attributes

Name
volatile int child_stopping
apr_thread_t * port_acquire_thread

Functions Documentation

function port_acquire_thread_fn

static void *APR_THREAD_FUNC port_acquire_thread_fn(
    apr_thread_t * ,
    void * data
)

function find_h3_server

static h3_server_conf * find_h3_server(
    server_rec * s,
    server_rec ** out_server
)

function h3_child_init

void h3_child_init(
    apr_pool_t * pchild,
    server_rec * s
)

Parameters:

  • pchild The child process pool.
  • s The server_rec for the listener's vhost.

ap_child_init hook: bring up the QUIC listener in the first available child process. Idempotent across children: subsequent children detect the port is already owned and skip socket creation.

function h3_c1_child_stopping

void h3_c1_child_stopping(
    apr_pool_t * pool,
    int graceful
)

Parameters:

  • pool The pool used for any teardown allocations.
  • graceful Non-zero for a graceful stop, zero for immediate.

Child-stop hook: tear down the QUIC listener, join worker threads, and release the SSL context. No-op if the child never owned the listener.

Attributes Documentation

variable child_stopping

static volatile int child_stopping = 0;

variable port_acquire_thread

static apr_thread_t * port_acquire_thread = NULL;

Source code

/*
 * Copyright (c) 2026 The mod_http3 Project Authors. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <httpd.h>

#include <http_config.h>
#include <http_log.h>

#include <apr_pools.h>
#include <apr_thread_proc.h>
#include <apr_time.h>

#include <unistd.h>

#include "h3.h"
#include "h3_check.h"
#include "h3_config.h"
#include "h3_io.h"
#include "h3_server.h"
#include "h3_socket.h"
#include "mod_http3.h"

static volatile int child_stopping = 0;

static apr_thread_t* port_acquire_thread = NULL;

struct port_acquire_args
{
    apr_pool_t* pchild;
    server_rec* vhost;
    h3_server_conf* conf;
};

static void* APR_THREAD_FUNC port_acquire_thread_fn(apr_thread_t* /*thread*/, void* data)
{
    struct port_acquire_args* args = data;
    while (!child_stopping)
    {
        int udp_fd = -1;
        apr_status_t rv = h3_socket_open(args->conf->h3_port, args->pchild, &udp_fd);
        if (rv == APR_EAGAIN)
        {
            apr_sleep(apr_time_from_msec(H3_PORT_ACQUIRE_RETRY_MS));
            continue;
        }
        if (rv != APR_SUCCESS)
        {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, args->vhost, "port acquirer: h3_socket_open failed, giving up");
            break;
        }
        if (child_stopping)
        {
            h3_socket_close(udp_fd);
            break;
        }
        if (h3_io_listen_start(args->pchild, args->vhost, args->conf, udp_fd) != APR_SUCCESS)
        {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, args->vhost, "port acquirer: h3_io_listen_start failed");
            h3_socket_close(udp_fd);
            break;
        }
        ap_log_error(APLOG_MARK, APLOG_INFO, 0, args->vhost, "port acquirer: pid=%d acquired QUIC port after retrying", getpid());
        break;
    }
    return NULL;
}

static h3_server_conf* find_h3_server(server_rec* s, server_rec** out_server)
{
    h3_server_conf* conf = NULL;
    server_rec* current = s;
    while (current)
    {
        h3_server_conf* tmp = ap_get_module_config(current->module_config, &http3_module);
        if (tmp && tmp->h3_cert_path && tmp->h3_key_path && !conf)
        {
            conf = tmp;
            conf->host_port = get_server_port(current);
            *out_server = current;
            break;
        }
        current = current->next;
    }
    return conf;
}

void h3_child_init(apr_pool_t* pchild, server_rec* s)
{
    server_rec* vhost = NULL;
    h3_server_conf* conf = find_h3_server(s, &vhost);
    if (!conf)
    {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "h3_child_init: no H3 cert/key configured, skipping");
        return;
    }

    int udp_fd = -1;
    apr_status_t rv = h3_socket_open(conf->h3_port, pchild, &udp_fd);
    if (rv == APR_EAGAIN)
    {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, vhost, "h3_child_init: pid=%d port already owned, will keep retrying in background", getpid());
        struct port_acquire_args* args = apr_palloc(pchild, sizeof(*args));
        args->pchild = pchild;
        args->vhost = vhost;
        args->conf = conf;
        apr_threadattr_t* attr = NULL;
        if (apr_threadattr_create(&attr, pchild) != APR_SUCCESS || apr_thread_create(&port_acquire_thread, attr, port_acquire_thread_fn, args, pchild) != APR_SUCCESS)
        {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, vhost, "h3_child_init: failed to start port acquirer thread");
            port_acquire_thread = NULL;
        }
        return;
    }
    if (rv != APR_SUCCESS)
    {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, vhost, "h3_child_init: h3_socket_open failed");
        return;
    }
    if (h3_io_listen_start(pchild, vhost, conf, udp_fd) != APR_SUCCESS)
    {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, vhost, "h3_child_init: h3_io_listen_start failed");
        h3_socket_close(udp_fd);
    }
}

void h3_c1_child_stopping(apr_pool_t* /*p*/, int graceful)
{
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "mod_http3: child stopping (graceful=%d)", graceful);
    child_stopping = 1;
    if (port_acquire_thread)
    {
        apr_status_t ignored;
        apr_thread_join(&ignored, port_acquire_thread);
        port_acquire_thread = NULL;
    }
    h3_io_listen_stop(child_h3_io);
}