Skip to content

Segmentation Fault MacOS with Python 3.13 #484

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
MCubek opened this issue Apr 17, 2025 · 5 comments
Open

Segmentation Fault MacOS with Python 3.13 #484

MCubek opened this issue Apr 17, 2025 · 5 comments
Labels
bug Something isn't working

Comments

@MCubek
Copy link

MCubek commented Apr 17, 2025

  1. What versions are you using?

Oracle ATP 19.c

platform.platform: macOS-15.4-arm64-arm-64bit-Mach-O
sys.maxsize > 2**32: True
platform.python_version: 3.13.3

oracledb.version: 3.1.0

  1. Is it an error or a hang or a crash?

Segmentation Fault Crash

  1. What error(s) or behavior you are seeing?

https://gist.github.com/MCubek/ffa2806d8bf0a8e73fe3ac4436d888ed

This issue does not happen on Python 3.12.

  1. Does your application call init_oracle_client()?

Yes, thick mode is used.

  1. Include a runnable Python script that shows the problem.

Test script below couldn't reproduce the issue but represents how the pool is created...

# Minimal settings (replace placeholders)
username = "your_user"
password = "your_password"
dsn = "your_dsn" 
wallet_path = "/path/to/your/oracle/wallet"
instant_client_path = "/path/to/your/instantclient"


if __name__ == "__main__":
    # Initialize Oracle client (thick mode)
    print("Initializing Oracle Client...")
    oracledb.init_oracle_client(lib_dir=instant_client_path, config_dir=wallet_path)

    # Attempt creating a connection pool (crashes here on Python 3.13)
    print("Creating Oracle connection pool...")
    pool = oracledb.create_pool(
        user=username,
        password=password,
        config_dir=wallet_path,
        dsn=dsn,
        wallet_location=wallet_path,
        wallet_password="",
        min=1,
        max=2,
        increment=1,
    )

    print("Acquiring connection from pool...")
    with pool.acquire() as connection:
        print("Oracle DB version:", connection.version)

    print("Test completed successfully (if no crash).")

In production FastAPI is used to create pool as shown in snippets:

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Create the connection pool
    app.state.pool = create_db_connection_pool()
    yield
    # Close the connection pool on shutdown
    app.state.pool.close()
    logger.debug("Closed Oracle database connection pool.")

if os.getenv("ENV_FOR_DYNACONF") != "test":
    # noinspection PyUnresolvedReferences
    oracledb.init_oracle_client(
        lib_dir=settings.db.instant_client_library, config_dir=wallet_path
    )


def create_db_connection_pool() -> oracledb.ConnectionPool:
    logger.debug("Creating Oracle database connection pool...")
    pool = oracledb.create_pool(
        user=username,
        password=password,
        config_dir=wallet_path,
        dsn=dsn,
        wallet_location=wallet_path,
        wallet_password="",
        min=1,  # Minimum number of connections in the pool
        max=5,  # Maximum number of connections in the pool
        increment=1,  # Number of connections to add when more are needed
    )
    logger.debug("Created Oracle database connection pool.")
    return cast("oracledb.ConnectionPool", pool)
@MCubek MCubek added the bug Something isn't working label Apr 17, 2025
@cjbj
Copy link
Member

cjbj commented Apr 17, 2025

We'll take a look. I fear this will be tricky to do anything with unless you can get a reproducible case, but maybe there is something obvious in the driver that can be tweaked. Do you know if it also happens on other platforms (e.g. Linux)? Is there any other info available about when it does / doesn't reproduce, e.g with different pool settings?

@cjbj cjbj changed the title Segmentation Fault MacOS 3.13 Segmentation Fault MacOS with Python 3.13 Apr 17, 2025
@MCubek
Copy link
Author

MCubek commented Apr 17, 2025

I have forgotten to note that this occurs about half to 2/3 of the time when starting the FastAPI webserver, not each time.
I will try test a bit without connection pooling and with different environments (eg. docker linux/amd64).

@MCubek
Copy link
Author

MCubek commented Apr 17, 2025

Can't reproduce the issue under docker linux/amd64 environment, and under macos seems to work more often now then before for some reason but still fails 1/4 of the time with seg fault.
No luck reproducing the issue in a smaller snippet yet.

@MCubek
Copy link
Author

MCubek commented Apr 22, 2025

Issue seems to originate from import of xgboost package.
from xgboost import XGBRegressor

When import exists in code and when running with debuger in pycharm more then half of the time the provided segmentation fault will occur. Other times runs just fine...

Xgboost library at version 3.0.0

@MCubek
Copy link
Author

MCubek commented Apr 22, 2025

from contextlib import asynccontextmanager
from http.client import HTTPException
from typing import Annotated, AsyncGenerator, cast

from xgboost import XGBRegressor
import oracledb
from fastapi import Depends, FastAPI, Request, status

username = "username"
password = "password"
dsn = "dns"
wallet_path = "/opt/oracle/wallet"
instant_client_path = "/opt/oracle/instantclient_23_3"

oracledb.init_oracle_client(lib_dir=instant_client_path, config_dir=wallet_path)


def create_db_connection_pool() -> oracledb.ConnectionPool:
    print("Creating Oracle database connection pool...")
    pool = oracledb.create_pool(
        user=username,
        password=password,
        config_dir=wallet_path,
        dsn=dsn,
        wallet_location=wallet_path,
        wallet_password="",
        min=1,  # Minimum number of connections in the pool
        max=5,  # Maximum number of connections in the pool
        increment=1,  # Number of connections to add when more are needed
    )
    print("Created Oracle database connection pool.")
    return cast("oracledb.ConnectionPool", pool)


async def get_tenant_schema(
    request: Request,
) -> str:
    # Try to get tenant_schema from headers
    tenant_schema = request.headers.get("tenant_schema")

    # If not in headers, try to get from query params
    if not tenant_schema:
        tenant_schema = request.query_params.get("tenant_schema")

    # If not in headers, try to get from query params
    if not tenant_schema:
        tenant_schema = request.query_params.get("tenantSchema")

    # If still not found and body may be present
    if not tenant_schema and request.method in ("POST", "PUT", "PATCH"):
        body = await request.json()
        tenant_schema = body.get("tenant_schema")

    # If still not found and body may be present
    if not tenant_schema and request.method in ("POST", "PUT", "PATCH"):
        body = await request.json()
        tenant_schema = body.get("tenantSchema")

    if not tenant_schema:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, detail="Tenant schema not provided"
        )

    return tenant_schema


async def get_db_connection_from_pool(
    request: Request, tenant_schema: str = Depends(get_tenant_schema)
) -> AsyncGenerator[oracledb.Connection]:
    pool = request.app.state.pool
    conn = pool.acquire()
    conn.current_schema = tenant_schema
    try:
        yield conn
    finally:
        conn.close()


@asynccontextmanager
async def lifespan(app: FastAPI):
    # Create the connection pool
    app.state.pool = create_db_connection_pool()

    print("Acquiring connection from pool...")
    with app.state.pool.acquire() as connection:
        print("Oracle DB version:" + connection.version)

    print("Test completed successfully (if no crash).")
    yield
    # Close the connection pool on shutdown
    app.state.pool.close()


app = FastAPI(
    title="test",
    description="test",
    version="1.0.0",
    lifespan=lifespan,
)


@app.get(
    "/database",
)
async def test_database_connection(
    tenant_schema: str | None,  # noqa: ARG001
    connection: Annotated[oracledb.Connection, Depends(get_db_connection_from_pool)],
):
    with connection.cursor() as cursor:
        cursor.execute("SELECT * FROM all_tables")
        result = cursor.fetchone()

        if result:
            print("works")


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=8000, log_config=None, log_level=None)

This chunk of code can be used to reproduce issue when running in debugger.
Failure rate is about 50% of the time.
Image shown shows a successful run with debugger command shown.

Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants