Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/coordination/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def run(endpoint, database):
threads = []

for i in range(4):
worker_name = f"worker {i+1}"
worker_name = f"worker {i + 1}"
if i < 2:
thread = threading.Thread(target=linear_workload, args=(driver.coordination_client, worker_name))
else:
Expand Down
2 changes: 1 addition & 1 deletion examples/topic/writer_example.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import concurrent.futures
import datetime
from typing import Dict, List
from concurrent.futures import Future, wait
from concurrent.futures import Future, wait # noqa: F401

import ydb
from ydb import TopicWriterMessage
Expand Down
2 changes: 1 addition & 1 deletion test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ zipp==3.19.1
aiohttp>=3.9.0
pytest-pep8
pytest-flake8
flake8==3.9.2
flake8==6.1.0
sqlalchemy==1.4.26
pylint-protobuf
cython
Expand Down
2 changes: 1 addition & 1 deletion tests/iam/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def __init__(self, account_id, key_id, private_key, iam_endpoint=None, iam_chann
self.iam_channel_credentials = iam_channel_credentials

def __eq__(self, other):
return self.__dict__ == other.__dict__ if type(self) == type(other) else False
return self.__dict__ == other.__dict__ if type(self) is type(other) else False


@patch("builtins.open", new_callable=mock_open, read_data=CONTENT1)
Expand Down
2 changes: 1 addition & 1 deletion tests/scheme/scheme_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import typing
import typing # noqa: F401
import pytest

import ydb
Expand Down
2 changes: 1 addition & 1 deletion tests/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def test_scheme_error(driver_sync, database):

server_code = ydb.issues.StatusCode.SCHEME_ERROR

assert type(exc.value) == ydb.issues.SchemeError
assert type(exc.value) is ydb.issues.SchemeError
assert exc.value.status == server_code
assert f"server_code: {server_code}" in str(exc.value)
assert "Path does not exist" in str(exc.value)
2 changes: 1 addition & 1 deletion tests/topics/test_topic_writer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import asyncio
from typing import List
from typing import List # noqa: F401

import pytest

Expand Down
19 changes: 14 additions & 5 deletions ydb/_topic_common/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,26 @@ def wrapper(rpc_state, response_pb, driver=None):


def _get_shared_event_loop() -> asyncio.AbstractEventLoop:
global _shared_event_loop

if _shared_event_loop is not None:
return _shared_event_loop

with _shared_event_loop_lock:
if _shared_event_loop is not None:
return _shared_event_loop

event_loop_set_done: concurrent.futures.Future[asyncio.AbstractEventLoop] = concurrent.futures.Future()
loop_ready: threading.Event = threading.Event()

def start_event_loop():
event_loop = asyncio.new_event_loop()
event_loop_set_done.set_result(event_loop)
asyncio.set_event_loop(event_loop)

def on_loop_started():
# Set global only when loop is actually running
global _shared_event_loop
_shared_event_loop = event_loop
loop_ready.set()

event_loop.call_soon(on_loop_started)
event_loop.run_forever()

t = threading.Thread(
Expand All @@ -60,7 +65,11 @@ def start_event_loop():
)
t.start()

_shared_event_loop = event_loop_set_done.result()
loop_ready.wait()

if _shared_event_loop is None:
raise RuntimeError("Event loop was not properly initialized")

return _shared_event_loop
Comment on lines 39 to 73
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a potential data race between the unsynchronized read of _shared_event_loop at line 39 and the write at line 55 (which happens in a different thread outside the lock). While this is likely safe in CPython due to the GIL making reference assignments atomic, it's not guaranteed to be safe in all Python implementations and violates proper synchronization patterns.

Consider capturing the event loop reference in a local variable within the lock and returning that, while still setting the global for future callers. This would eliminate the data race and make the code more robust.

Copilot uses AI. Check for mistakes.


Expand Down
4 changes: 2 additions & 2 deletions ydb/retries.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def __init__(self, timeout: float) -> None:

def __eq__(self, other: object) -> bool:
return (
type(self) == type(other) and isinstance(other, YdbRetryOperationSleepOpt) and self.timeout == other.timeout
type(self) is type(other) and isinstance(other, YdbRetryOperationSleepOpt) and self.timeout == other.timeout
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isinstance check is redundant after type(self) is type(other). If type(self) is type(other) is True, then other must be an instance of YdbRetryOperationSleepOpt (assuming self is). The isinstance check can be safely removed without changing the behavior.

Copilot uses AI. Check for mistakes.
)

def __repr__(self) -> str:
Expand All @@ -91,7 +91,7 @@ def __init__(self, result: Any) -> None:

def __eq__(self, other: object) -> bool:
return (
type(self) == type(other)
type(self) is type(other)
and isinstance(other, YdbRetryOperationFinalResult)
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isinstance check is redundant after type(self) is type(other). If type(self) is type(other) is True, then other must be an instance of YdbRetryOperationFinalResult (assuming self is). The isinstance check can be safely removed without changing the behavior.

Copilot uses AI. Check for mistakes.
and self.result == other.result
and self.exc == other.exc
Expand Down
4 changes: 2 additions & 2 deletions ydb/table_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def test_retry_operation_impl(monkeypatch):
monkeypatch.setattr(
issues.Error,
"__eq__",
lambda self, other: type(self) == type(other) and self.message == other.message,
lambda self, other: type(self) is type(other) and self.message == other.message,
)

retry_once_settings = RetrySettings(
Expand Down Expand Up @@ -43,7 +43,7 @@ def __init__(self, message):
self.message = message

def __eq__(self, other):
return type(self) == type(other) and self.message == other.message
return type(self) is type(other) and self.message == other.message

def check_unretriable_error(err_type, call_ydb_handler):
retry_once_settings.on_ydb_error_callback.reset_mock()
Expand Down
Loading