Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
cbe98dc
Initial asyncio commit
yuce Sep 19, 2025
162fd17
Updates
yuce Sep 19, 2025
9931956
Updates
yuce Sep 19, 2025
856e3df
Merge branch 'master' into asyncio-module
yuce Sep 19, 2025
35384bf
Black
yuce Sep 19, 2025
fdda120
Updates
yuce Sep 19, 2025
fee5b45
Updates
yuce Sep 19, 2025
fc2c38b
Updates
yuce Sep 19, 2025
1772031
Removed docs, include HazelcastClient/Map in public API
yuce Sep 19, 2025
170cf89
Updates
yuce Sep 19, 2025
539c904
Merge branch 'master' into asyncio-module
yuce Sep 22, 2025
22449a8
black
yuce Sep 22, 2025
5406bc6
Ignore incorrect mypy errors
yuce Sep 22, 2025
a417a4a
Updates
yuce Sep 24, 2025
d00c480
Updates
yuce Sep 25, 2025
baa3bc1
Annotate optional params
yuce Sep 29, 2025
ebfc9e2
black
yuce Sep 29, 2025
6928837
Remove update to test util
yuce Sep 30, 2025
3e03cbf
black
yuce Sep 30, 2025
51ced7a
black
yuce Sep 30, 2025
e635b94
update
yuce Sep 30, 2025
4f103f6
Added support for SSL
yuce Sep 30, 2025
042cc58
Added SSL tests
yuce Sep 30, 2025
265a2b4
Added mutual authentication test
yuce Sep 30, 2025
293975d
Added hostname verification tests
yuce Oct 1, 2025
2718478
black
yuce Oct 1, 2025
58783dc
Ported more integration tests
yuce Oct 1, 2025
3cf9982
Ported hazelcast json value test
yuce Oct 2, 2025
7e97ec7
Merge branch 'master' into asyncio-module-integration-tests1
yuce Oct 2, 2025
6a558e8
Merge branch 'master' into asyncio-module-ssl
yuce Oct 2, 2025
a630706
Merge branch 'master' into asyncio-module
yuce Oct 2, 2025
c1798ea
Ported heart beat test
yuce Oct 2, 2025
e92936a
Ported more tests
yuce Oct 20, 2025
6ced889
Merge branch 'master' into asyncio-module
yuce Oct 20, 2025
c313bfa
Merge branch 'master' into asyncio-module-ssl
yuce Oct 20, 2025
6222c6b
Merge branch 'master' into asyncio-module-integration-tests1
yuce Oct 20, 2025
120a58a
black
yuce Oct 22, 2025
80880b8
Fixed type hints
yuce Oct 30, 2025
6431acc
type hints
yuce Nov 14, 2025
e9a9b5e
Ported more tests
yuce Nov 17, 2025
5334cd1
Added near cache, statistics, statistics tests
yuce Nov 17, 2025
a14290a
Black
yuce Nov 17, 2025
492ccc1
Fixed getting local address
yuce Nov 18, 2025
e8a2600
Fixed getting local address, take 2
yuce Nov 18, 2025
24eb6bf
Added nearcache tests
yuce Nov 18, 2025
6ab9365
Ported missing nearcache test
yuce Nov 18, 2025
3f3a9c5
Ported VectorCollection and its tests
yuce Nov 19, 2025
bfb805d
Black
yuce Nov 19, 2025
5f59992
Ported compact serialization tests
yuce Nov 20, 2025
91bf1d1
Addressed review comment
yuce Nov 21, 2025
ef7570f
Updates
yuce Nov 21, 2025
2128f5e
Removed unnecessary code
yuce Nov 21, 2025
62697e3
Add BETA warning
yuce Nov 21, 2025
a87a5c6
Black
yuce Nov 21, 2025
5023568
Updated test_heartbeat_stopped_and_restored test
yuce Nov 24, 2025
8d7eede
Merge branch 'asyncio-module' into asyncio-module-ssl
yuce Nov 24, 2025
00a2d12
Merge branch 'asyncio-module-ssl' into asyncio-module-integration-tests1
yuce Nov 24, 2025
539466b
Merge branch 'asyncio-module-integration-tests1' into asyncio-module-…
yuce Nov 24, 2025
2aff5e4
Merge branch 'asyncio-module-integration-tests2' into asyncio-module-…
yuce Nov 24, 2025
284de6d
Merge branch 'asyncio-module-vc-support' into asyncio-module-compact-…
yuce Nov 24, 2025
767bfd5
Fix test_map_smart_listener_local_only
yuce Nov 24, 2025
e673679
Updated test_heartbeat_stopped_and_restored
yuce Nov 25, 2025
ab4a746
Merge branch 'asyncio-module-integration-tests1' into asyncio-module-…
yuce Nov 25, 2025
319bb35
Fixed tests
yuce Nov 25, 2025
8e325ea
Linter
yuce Nov 25, 2025
eed53b3
Test updates
yuce Nov 25, 2025
a6d5949
Merge branch 'asyncio-module-integration-tests2' into asyncio-module-…
yuce Nov 26, 2025
f61ec8e
Test updates
yuce Nov 26, 2025
4446ba7
Merge branch 'asyncio-module-vc-support' into asyncio-module-compact-…
yuce Nov 26, 2025
1ca7fd6
Addressed review comment
yuce Nov 26, 2025
1c1699d
Update
yuce Nov 28, 2025
d9acede
updates
yuce Nov 28, 2025
bd23f41
Merge branch 'asyncio-module-ssl' into asyncio-module-integration-tests1
yuce Nov 28, 2025
76759ec
Merge branch 'asyncio-module-integration-tests1' into asyncio-module-…
yuce Nov 28, 2025
74a9aca
Merge branch 'asyncio-module-integration-tests2' into asyncio-module-…
yuce Nov 28, 2025
e33af1d
Merge branch 'asyncio-module-vc-support' into asyncio-module-compact-…
yuce Nov 28, 2025
81b5041
Merge branch 'asyncio-module-compact-updates' into asyncio-module-clo…
yuce Nov 28, 2025
2a2d6e8
Merge branch 'master' into asyncio-module-integration-tests1
yuce Dec 1, 2025
6da4226
Merge branch 'asyncio-module-integration-tests1' into asyncio-module-…
yuce Dec 1, 2025
0a829d4
Merge branch 'asyncio-module-integration-tests2' into asyncio-module-…
yuce Dec 2, 2025
3cf71d5
Merge branch 'asyncio-module-vc-support' into asyncio-module-compact-…
yuce Dec 2, 2025
d61731d
Merge branch 'asyncio-module-compact-updates' into asyncio-module-clo…
yuce Dec 2, 2025
9e1a2e6
Preconn buffer and reactor close task
yuce Dec 4, 2025
6c75f4a
Removed CPSubsystem and ProxySessionManager from the client
yuce Dec 4, 2025
7ad8c4d
Fix
yuce Dec 4, 2025
550a006
Merge branch 'master' into asyncio-module-integration-tests2
yuce Dec 4, 2025
2c8f10e
Prevent deadlock
yuce Dec 4, 2025
7f92a93
Merge branch 'asyncio-module-integration-tests2' into asyncio-module-…
yuce Dec 4, 2025
4a741ae
Merge branch 'master' into asyncio-module-vc-support
yuce Dec 4, 2025
264e9fa
Merge branch 'asyncio-module-vc-support' into asyncio-module-compact-…
yuce Dec 4, 2025
df561e1
Refactored Proxy.destroy to clarify
yuce Dec 4, 2025
a4697a4
Merge branch 'asyncio-module-vc-support' into asyncio-module-compact-…
yuce Dec 5, 2025
207d9e6
Merge branch 'asyncio-module-compact-updates' into asyncio-module-clo…
yuce Dec 5, 2025
f863f58
Merge branch 'asyncio-module-cloud-support' into asyncio-module-cloud…
yuce Dec 5, 2025
082f02c
More updates
yuce Dec 5, 2025
fddd197
Black
yuce Dec 5, 2025
f2378cd
merged with master
yuce Dec 5, 2025
eb60360
Merge branch 'asyncio-module-compact-updates' into asyncio-module-clo…
yuce Dec 5, 2025
3a9d4f5
Merge branch 'master' into asyncio-module-cloud-support
yuce Dec 5, 2025
59b667e
Merge branch 'asyncio-module-cloud-support' into asyncio-module-cloud…
yuce Dec 5, 2025
f86e6f0
Self-create connection socket
yuce Dec 5, 2025
d73b3a9
Updated version
yuce Dec 5, 2025
759c97e
Lint
yuce Dec 5, 2025
2db5c1d
Improved reliability, unskip skipped VC tests
yuce Dec 6, 2025
ab8ca27
Black
yuce Dec 6, 2025
b724f52
Try Windows fix
yuce Dec 6, 2025
0f191c8
Close socket
yuce Dec 6, 2025
60687b8
Ported the soak test to asyncio
yuce Dec 8, 2025
453e739
Merge branch 'master' into asyncio-module-soak-tests
yuce Dec 8, 2025
6cb5b17
Stop clients on exit
yuce Dec 9, 2025
c01fe4f
Fix getting raw socket; updated soak tests
yuce Dec 9, 2025
ec5896f
Updated start_members script
yuce Dec 9, 2025
0ef1822
Black
yuce Dec 9, 2025
3dfb065
Trivial
yuce Dec 9, 2025
15772fe
Remove Python 3.7
yuce Dec 9, 2025
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 hazelcast/asyncio/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import warnings

warnings.warn("Asyncio API for Hazelcast Python Client is in BETA. DO NOT use it in production.")
warnings.warn("Asyncio API for Hazelcast Python Client is BETA. DO NOT use it in production.")
del warnings

__all__ = ["HazelcastClient", "Map"]
Expand Down
5 changes: 1 addition & 4 deletions hazelcast/internal/asyncio_reactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,7 @@ async def _create_connection(self, config, address):
self._connected = True

sock, self._proto = res
if hasattr(sock, "_ssl_protocol"):
sock = sock._ssl_protocol._transport._sock
else:
sock = sock._sock
sock = sock.get_extra_info("socket")
sockname = sock.getsockname()
host, port = sockname[0], sockname[1]
self.local_address = Address(host, port)
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.7",
Copy link
Contributor

Choose a reason for hiding this comment

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

how come this was not caught before? Dont we have a test to verify all supported runtimes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can't test for these.
These are just annotations.

"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
Expand Down
2 changes: 1 addition & 1 deletion tests/soak_test/hazelcast.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/config
https://www.hazelcast.com/schema/config/hazelcast-config-5.0.xsd">
https://www.hazelcast.com/schema/config/hazelcast-config-5.6.xsd">
<network>
<join>
<multicast enabled="true">
Expand Down
268 changes: 268 additions & 0 deletions tests/soak_test/map_soak_test_asyncio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
import argparse
import asyncio
import logging
import os
import random
import sys
import time
import typing
from collections import defaultdict

sys.path.append(os.path.join(os.path.dirname(__file__), "../../"))
# Making sure that hazelcast directory is in the sys.path so we can import modules from there in the command line.

from hazelcast.asyncio import HazelcastClient, Map
from hazelcast.predicate import between
from hazelcast.serialization.api import IdentifiedDataSerializable

if hasattr(time, "monotonic"):
get_current_timestamp = time.monotonic
else:
get_current_timestamp = time.time


TASK_COUNT = 256
ENTRY_COUNT = 10000
OBSERVATION_INTERVAL = 10.0


class SoakTestCoordinator:
def __init__(self, test_duration, address):
self.address = address
self._task_count_before = len(asyncio.all_tasks())
self._deadline = get_current_timestamp() + test_duration * 60 * 60
self._lock = asyncio.Lock()
# the following are protected by the lock above
self._reached_deadline = False
self._tests_failed = False

async def start_tests(self):
client, test_map = await self._init_client_map()
logging.info("Soak test operations are starting!")
logging.info("* " * 20 + "\n")
test_runners = [TestRunner(i, self) for i in range(TASK_COUNT)]
observer = OperationCountObserver(self, test_runners)
async with asyncio.TaskGroup() as tg:
observer_task = tg.create_task(observer.run())
for runner in test_runners:
tg.create_task(runner.run(test_map))

await observer_task

logging.info("* " * 20)
logging.info("Soak test has finished!")
logging.info("-" * 40)

if await self.tests_failed():
logging.info("Soak test failed!")
return

hang_counts = await observer.hang_counts()
if not hang_counts:
logging.info("All threads worked without hanging")
else:
for runner, hang_count in hang_counts:
logging.info("Thread %s hanged %s times.", runner.id, hang_count)

await client.shutdown()
# wait for canceled tasks to expire
await asyncio.sleep(1)
task_count_after = len(asyncio.all_tasks())
logging.info("Task count before: %s, after: %s", self._task_count_before, task_count_after)

async def notify_error(self):
async with self._lock:
self._tests_failed = True

async def should_continue_tests(self):
async with self._lock:
return not (self._reached_deadline or self._tests_failed)

async def check_deadline(self):
async with self._lock:
now = get_current_timestamp()
self._reached_deadline = now >= self._deadline

async def tests_failed(self) -> bool:
async with self._lock:
return self._tests_failed

async def _init_client_map(self) -> typing.Tuple[HazelcastClient, Map]:
def no_op_listener(_):
pass

try:
client = await HazelcastClient.create_and_start(
cluster_members=[self.address],
data_serializable_factories={
SimpleEntryProcessor.FACTORY_ID: {
SimpleEntryProcessor.CLASS_ID: SimpleEntryProcessor
}
},
)
map = await client.get_map("test-map")
await map.add_entry_listener(
include_value=False,
added_func=no_op_listener,
removed_func=no_op_listener,
updated_func=no_op_listener,
)
return client, map

except Exception as e:
logging.exception("Client failed to start")
raise e


class TestRunner:
def __init__(self, id, coordinator: SoakTestCoordinator):
self.id = id
self.coordinator = coordinator
self.counter = OperationCounter()

async def run(self, test_map):
coordinator = self.coordinator
processor = SimpleEntryProcessor("test")
while await coordinator.should_continue_tests():
key = str(random.randint(0, ENTRY_COUNT))
value = str(random.randint(0, ENTRY_COUNT))
operation = random.randint(0, 100)
try:
if operation < 30:
await test_map.get(key)
elif operation < 60:
await test_map.put(key, value)
elif operation < 80:
await test_map.values(between("this", 0, 10))
else:
await test_map.execute_on_key(key, processor)

await self.counter.increment()
except Exception:
await coordinator.notify_error()
logging.exception("Unexpected error occurred in thread %s", self)
return


class OperationCountObserver:
def __init__(self, coordinator, test_runners):
self.coordinator = coordinator
self.test_runners = test_runners
self._lock = asyncio.Lock()
# the lock above protects the fields below
self._hang_counts = defaultdict(int)

async def hang_counts(self):
async with self._lock:
return self._hang_counts

async def _increment_hang_count(self, runner):
async with self._lock:
self._hang_counts[runner] += 1

async def run(self):
while True:
await asyncio.sleep(OBSERVATION_INTERVAL)
await self.coordinator.check_deadline()
if not await self.coordinator.should_continue_tests():
break

logging.info("-" * 40)
op_count = 0
hanged_runners = []

for test_runner in self.test_runners:
op_count_per_runner = await test_runner.counter.get_and_reset()
op_count += op_count_per_runner
if op_count == 0:
hanged_runners.append(test_runner)

if not hanged_runners:
logging.info("All threads worked without hanging")
else:
logging.info("%s threads hanged: %s", len(hanged_runners), hanged_runners)
for hanged_runner in hanged_runners:
await self._increment_hang_count(hanged_runner)

logging.info("-" * 40)
logging.info("Operations Per Second: %s\n", op_count / OBSERVATION_INTERVAL)


class OperationCounter:
def __init__(self):
self._count = 0
self._lock = asyncio.Lock()

async def get_and_reset(self):
async with self._lock:
total = self._count
self._count = 0
return total

async def increment(self):
async with self._lock:
self._count += 1


class SimpleEntryProcessor(IdentifiedDataSerializable):
CLASS_ID = 1
FACTORY_ID = 66

def __init__(self, value):
self.value = value

def read_data(self, object_data_input):
pass

def write_data(self, object_data_output):
object_data_output.write_string(self.value)

def get_class_id(self):
return self.CLASS_ID

def get_factory_id(self):
return self.FACTORY_ID


def parse_arguments():
parser = argparse.ArgumentParser()
parser.add_argument(
"--duration",
default=48.0,
type=float,
help="Duration of the test in hours",
)
parser.add_argument(
"--address",
required=True,
type=str,
help="host:port of the one of the cluster members",
)
parser.add_argument(
"--log-file",
required=True,
type=str,
help="Name of the log file",
)
return parser.parse_args()


def setup_logging(log_file):
logging.basicConfig(
filename=log_file,
filemode="w",
format="%(asctime)s %(message)s",
datefmt="%H:%M:%S",
level=logging.INFO,
)


async def amain():
arguments = parse_arguments()
setup_logging(arguments.log_file)
coordinator = SoakTestCoordinator(arguments.duration, arguments.address)
await coordinator.start_tests()


if __name__ == "__main__":
asyncio.run(amain())
37 changes: 31 additions & 6 deletions tests/soak_test/start_clients.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,35 @@ DURATION=${2:-48.0}

mkdir -p "client_logs"

for i in {0..9}
do
python map_soak_test.py \
--duration "$DURATION" \
--address "$ADDRESS" \
--log-file client_logs/client-"$i" &
declare -a pids

cleanup () {
for pid in "${pids[@]}"; do
echo "Stopping $pid"
kill "$pid"
done
}

trap cleanup EXIT

for i in {1..5}; do
python map_soak_test_asyncio.py \
--duration "$DURATION" \
--address "$ADDRESS" \
--log-file client_logs/client-asyncio-"$i" &
pid=$!
echo "$pid running"
pids+=("$pid")
done

for i in {1..5}; do
Copy link
Contributor

Choose a reason for hiding this comment

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

there used to be 10 clients all same type. Now you mıxed asyncio and non-asyncio usage. But this is not an expected usage from users. I would keep 10 clients and have 2 different tests for asynci and asyncore. We need to make sure both clients work independently in their own cluster without a problem. Mixed usage is another use case which is less interest.

Copy link
Contributor Author

@yuce yuce Dec 9, 2025

Choose a reason for hiding this comment

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

Users can use both asyncio and asyncore clients together without problems even in the same process.

In the soak tests, asyncio and asyncore clients run in different processes, so there's even better isolation.

Copy link
Contributor

Choose a reason for hiding this comment

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

We do not do such mixed tests for other clients which is also possible. If you want to change the soak test in this way, we better evaluate changing it for all clients. Original soak test did not mean to test this scenario.

python map_soak_test.py \
--duration "$DURATION" \
--address "$ADDRESS" \
--log-file client_logs/client-"$i" &
pid=$!
echo "$pid running"
pids+=("$pid")
done

wait
18 changes: 17 additions & 1 deletion tests/soak_test/start_members.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
#!/bin/bash

VERSION="5.0"
VERSION="5.6.0"

mkdir -p "member_logs"

declare -a pids

cleanup () {
for pid in "${pids[@]}"; do
echo "Stopping $pid"
kill "$pid"
done
}

trap cleanup EXIT

CLASSPATH="hazelcast-${VERSION}.jar:hazelcast-${VERSION}-tests.jar"
CMD_CONFIGS="-Djava.net.preferIPv4Stack=true"

Expand All @@ -13,4 +24,9 @@ do
com.hazelcast.core.server.HazelcastMemberStarter \
1> member_logs/hazelcast-err-"$i" \
2> member_logs/hazelcast-out-"$i" &
pid=$!
echo "$pid running"
pids+=("$pid")
done

wait