Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 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
9093e27
Addressed review comment
yuce Dec 8, 2025
f724d2d
Delete unused variable
yuce Dec 8, 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
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@ test:
test-enterprise:
pytest --verbose

test-asyncio:
pytest --verbose -k asyncio

test-cover:
pytest --cov=hazelcast --cov-report=xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import asyncio

from hazelcast.asyncio import HazelcastClient


async def amain():
client = await HazelcastClient.create_and_start(
# Set up cluster name for authentication
cluster_name="asyncio",
# Set the token of your cloud cluster
cloud_discovery_token="wE1w1USF6zOnaLVjLZwbZHxEoZJhw43yyViTbe6UBTvz4tZniA",
ssl_enabled=True,
ssl_cafile="/path/to/ca.pem",
ssl_certfile="/path/to/cert.pem",
ssl_keyfile="/path/to/key.pem",
ssl_password="05dd4498c3f",
)
my_map = await client.get_map("map-on-the-cloud")
await my_map.put("key", "value")

value = await my_map.get("key")
print(value)

await client.shutdown()


if __name__ == "__main__":
asyncio.run(amain())
2 changes: 0 additions & 2 deletions examples/cloud-discovery/hazelcast_cloud_discovery_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
cluster_name="YOUR_CLUSTER_NAME",
# Set the token of your cloud cluster
cloud_discovery_token="YOUR_CLUSTER_DISCOVERY_TOKEN",
# If you have enabled encryption for your cluster, also configure TLS/SSL for the client.
# Otherwise, skip options below.
ssl_enabled=True,
ssl_cafile="/path/to/ca.pem",
ssl_certfile="/path/to/cert.pem",
Expand Down
2 changes: 1 addition & 1 deletion hazelcast/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "6.0.0"
__version__ = "5.6.0"

# Set the default handler to "hazelcast" loggers
# to avoid "No handlers could be found" warnings.
Expand Down
4 changes: 2 additions & 2 deletions hazelcast/asyncio/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from hazelcast.internal.asyncio_cluster import ClusterService, _InternalClusterService
from hazelcast.internal.asyncio_compact import CompactSchemaService
from hazelcast.config import Config, IndexConfig
from hazelcast.internal.asyncio_connection import ConnectionManager, DefaultAddressProvider
from hazelcast.internal.asyncio_connection import ConnectionManager, DefaultAsyncioAddressProvider
from hazelcast.core import DistributedObjectEvent, DistributedObjectInfo
from hazelcast.discovery import HazelcastCloudAddressProvider
from hazelcast.errors import IllegalStateError, InvalidConfigurationError
Expand Down Expand Up @@ -313,7 +313,7 @@ def _create_address_provider(self):
connection_timeout = self._get_connection_timeout(config)
return HazelcastCloudAddressProvider(cloud_discovery_token, connection_timeout)

return DefaultAddressProvider(cluster_members)
return DefaultAsyncioAddressProvider(cluster_members)

def _create_client_name(self, client_id):
client_name = self._config.client_name
Expand Down
49 changes: 25 additions & 24 deletions hazelcast/internal/asyncio_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def __init__(
self._cluster_id = None
self._load_balancer = None
self._use_public_ip = (
isinstance(address_provider, DefaultAddressProvider) and config.use_public_ip
isinstance(address_provider, DefaultAsyncioAddressProvider) and config.use_public_ip
)
# asyncio tasks are weakly referenced
# storing tasks here in order not to lose them midway
Expand Down Expand Up @@ -385,9 +385,10 @@ async def _get_or_connect_to_address(self, address):
for connection in list(self.active_connections.values()):
if connection.remote_address == address:
return connection
translated = self._translate(address)
connection = await self._create_connection(translated)
response = await self._authenticate(connection)
translated = await self._translate(address)
connection = self._create_connection(translated)
await connection._create_task
response = self._authenticate(connection)
await self._on_auth(response, connection)
return connection

Expand All @@ -396,23 +397,24 @@ async def _get_or_connect_to_member(self, member):
if connection:
return connection

translated = self._translate_member_address(member)
connection = await self._create_connection(translated)
response = await self._authenticate(connection)
translated = await self._translate_member_address(member)
connection = self._create_connection(translated)
await connection._create_task
Copy link
Member

Choose a reason for hiding this comment

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

What's the reason not returning a "future" on self._create_connection but instead of doing it on connection._create_task ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

_create_connection returns the AsyncioConnection object itself, since it is used in _authenticate.
_create_connection also creates two tasks, one is for creating the socket, and the second is for checking whether the connection was established in time.
We await on connection._create_task to make sure the socket is created before carrying on with authentication.

Copy link
Member

Choose a reason for hiding this comment

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

Still, it looks odd to me. Instead of tracking an internal field, I would make it a method which is called after initializing the object. Also, I think both connect and auth can be combined into one. Nevertheless, I won't stand on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

_create_task may not be the best name.
It is not like "create this task", but "task named create".
Even if I add a method like:

async def ensure_connected(self):
   await self._create_task

I would still have to call it with await:

connection = self._create_connection(translated)
await connection.ensure_connected()

So that doesn't buy us much.

response = self._authenticate(connection)
await self._on_auth(response, connection)
return connection

async def _create_connection(self, address):
return await self._reactor.connection_factory(
def _create_connection(self, address):
return self._reactor.connection_factory(
self,
self._connection_id_generator.get_and_increment(),
address,
self._config,
self._invocation_service.handle_client_message,
)

def _translate(self, address):
translated = self._address_provider.translate(address)
async def _translate(self, address):
translated = await self._address_provider.translate(address)
if not translated:
raise ValueError(
"Address provider %s could not translate address %s"
Expand All @@ -421,15 +423,15 @@ def _translate(self, address):

return translated

def _translate_member_address(self, member):
async def _translate_member_address(self, member):
if self._use_public_ip:
public_address = member.address_map.get(_CLIENT_PUBLIC_ENDPOINT_QUALIFIER, None)
if public_address:
return public_address

return member.address

return self._translate(member.address)
return await self._translate(member.address)

async def _trigger_cluster_reconnection(self):
if self._reconnect_mode == ReconnectMode.OFF:
Expand Down Expand Up @@ -529,7 +531,8 @@ async def _sync_connect_to_cluster(self):
if connection:
return

for address in self._get_possible_addresses():
addresses = await self._get_possible_addresses()
for address in addresses:
self._check_client_active()
if address in tried_addresses_per_attempt:
# We already tried this address on from the member list
Expand Down Expand Up @@ -614,6 +617,7 @@ def _authenticate(self, connection) -> asyncio.Future:

async def _on_auth(self, response, connection):
try:
response = await response
response = client_authentication_codec.decode_response(response)
except Exception as e:
await connection.close_connection("Failed to authenticate connection", e)
Expand Down Expand Up @@ -790,8 +794,8 @@ def _check_client_active(self):
if not self._lifecycle_service.running:
raise HazelcastClientNotActiveError()

def _get_possible_addresses(self):
primaries, secondaries = self._address_provider.load_addresses()
async def _get_possible_addresses(self):
primaries, secondaries = await self._address_provider.load_addresses()
if self._shuffle_member_list:
# The relative order between primary and secondary addresses should
# not be changed. So we shuffle the lists separately and then add
Expand Down Expand Up @@ -1028,17 +1032,13 @@ def __hash__(self):
return self._id


class DefaultAddressProvider:
"""Provides initial addresses for client to find and connect to a node.

It also provides a no-op translator.
"""

class DefaultAsyncioAddressProvider:
def __init__(self, addresses):
self._addresses = addresses

def load_addresses(self):
async def load_addresses(self):
"""Returns the possible primary and secondary member addresses to connect to."""
# NOTE: This method is marked with async since the caller assumes that.
configured_addresses = self._addresses

if not configured_addresses:
Expand All @@ -1053,9 +1053,10 @@ def load_addresses(self):

return primaries, secondaries

def translate(self, address):
async def translate(self, address):
"""No-op address translator.

It is there to provide the same API with other address providers.
"""
# NOTE: This method is marked with async since the caller assumes that.
return address
58 changes: 58 additions & 0 deletions hazelcast/internal/asyncio_discovery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import asyncio
import logging

from hazelcast.discovery import HazelcastCloudDiscovery

_logger = logging.getLogger(__name__)


class HazelcastCloudAddressProvider:
"""Provides initial addresses for client to find and connect to a node
and resolves private IP addresses of Hazelcast Cloud service.
"""

def __init__(self, token, connection_timeout):
self.cloud_discovery = HazelcastCloudDiscovery(token, connection_timeout)
self._private_to_public = dict()

async def load_addresses(self):
"""Loads member addresses from Hazelcast Cloud endpoint.

Returns:
tuple[list[hazelcast.core.Address], list[hazelcast.core.Address]]: The possible member addresses
as primary addresses to connect to.
"""
try:
nodes = await asyncio.to_thread(self.cloud_discovery.discover_nodes)
# Every private address is primary
return list(nodes.keys()), []
except Exception as e:
_logger.warning("Failed to load addresses from Hazelcast Cloud: %s", e)
return [], []

async def translate(self, address):
"""Translates the given address to another address specific to network or service.

Args:
address (hazelcast.core.Address): Private address to be translated

Returns:
hazelcast.core.Address: New address if given address is known, otherwise returns None
"""
if address is None:
return None

public_address = self._private_to_public.get(address, None)
if public_address:
return public_address

await self.refresh()

return self._private_to_public.get(address, None)

async def refresh(self):
"""Refreshes the internal lookup table if necessary."""
try:
self._private_to_public = self.cloud_discovery.discover_nodes()
except Exception as e:
_logger.warning("Failed to load addresses from Hazelcast Cloud: %s", e)
Loading