synology_apm_repo.sdk.storage.smb module¶
SmbStore — a real, executable ObjectStore implementation for a
repository reached over the MS-SMB protocol (SMB2/3) rather than a
locally-mounted share.
smbprotocol is imported lazily, inside __init__ and each method,
rather than at module scope — the same rationale storage/s3.py’s module
docstring gives for aioboto3: it’s always installed (a required
dependency), but this module is imported unconditionally by
storage/__init__.py, so a module-level import would make every caller
of storage pay for it even when it never touches SMB.
Unlike S3Store/AzureStore, smbprotocol has no native-async
surface (no aiohttp foundation to sit on) — its smbclient submodule
is a synchronous, os-shaped API (open_file/stat/listdir,
file objects with seek/read). This store’s four methods are thin
asyncio.to_thread() wrappers around that synchronous body, the same
shape LocalFsStore uses for os.pread/os.fstat/iterdir —
not the native-async pattern the other two network backends use.
smbclient’s session/connection bookkeeping is process-wide by default
(keyed by server/port), which would let aclose() on one SmbStore
tear down a connection a sibling instance sharing the same server is still
using. Every call here passes its own connection_cache dict instead,
scoping each underlying SMB session to this instance alone — the same
per-instance isolation S3Store/AzureStore get for free from owning
their own client object.
This store keeps a small pool of independent sessions
(_DEFAULT_CONNECTION_POOL_SIZE), not one shared session — see
_ConnectionSlot and _call_with_retry for why: an SMB2 connection’s
own client-side flow-control window (smbprotocol’s “credits”) starts at
exactly 1 and only grows as the server grants more back on replies, so any
two requests genuinely in flight at once on one connection race for that
single credit and one of them gets a raised smbprotocol.exceptions.
SMBException. Rather than track that live, per-connection credit count
(which would mean reaching into smbprotocol internals this module
otherwise stays well clear of), every operation is instead routed through
one of a small number of independent connections, each never handling more
than one in-flight request at a time — real, safe parallelism up to the
pool size, without ever needing to know how large any one connection’s
window currently is.
One contract difference from LocalFsStore, in the other direction from
S3Store/AzureStore: an SMB share has real directory entities (like
a local filesystem, unlike an object store’s prefixes), so listdir on
a missing path raises NotFoundError, matching LocalFsStore rather than
the S3/Azure prefix-probing behavior.
Unlike S3Store/AzureStore, smbprotocol gives this module no
timeout/retry knobs of its own to configure past the initial connect —
a stalled share would otherwise block a call forever, and neither a
connect nor a later operation failure carries any signal distinguishing
“worth retrying” from “never will be” — so this module adds both itself,
via _DEFAULT_OPERATION_TIMEOUT/_DEFAULT_MAX_ATTEMPTS below.
- class synology_apm_repo.sdk.storage.smb.SmbStore(share, *, server, port=445, username=None, password=None)¶
Bases:
objectOne SMB share, addressed by
"/"-separated paths relative to the share root — the SMB equivalent ofS3Store’s bucket/AzureStore’s container.usernameaccepts the Windows-nativeDOMAIN\username(oruser@domainUPN) form directly;smbprotocol’s own NTLM/SPNEGO layer splits the domain back out of that single string, so this class has no separatedomainfield to keep in sync with it. Leavingusername/passwordunset attempts an anonymous/guest session, same as leaving S3/Azure’s credential fields unset falls back to their own ambient credential chains.Backed by a small pool of
_DEFAULT_CONNECTION_POOL_SIZEindependent SMB sessions, not one shared session — an SMB2 connection’s own client-side flow-control window starts at exactly 1, so two requests in flight at once on one connection would race for that single credit — each created lazily on first checkout rather than in__init__(which does no I/O). A call checks out whichever slot is currently free — preferring one already connected, via a LIFO free-stack, so sequential, non-overlapping calls keep reusing the same session instead of needlessly spreading across the whole pool — and holds it exclusively until that call (including any of its own retries) finishes.- async aclose()¶
Tear down every pooled slot’s own SMB session that was ever used — safe to call more than once, and scoped to this instance’s own slots alone (each with its own private
connection_cache, rather than sharingsmbclient’s process-wide, server-keyed session bookkeeping), so it never disturbs anotherSmbStoresharing the same server.Acquires every slot’s own checkout permit first (the same semaphore
_acquire_slotuses, drained down to zero free slots) before touching any slot’sready/connection_cache— a slot still checked out to an in-flight_call_with_retrycaller (mid_ensure_slot_session, or mid read/stat/listdir on an already -established one) can’t have its own permit acquired here until that caller releases it, so this never tears a slot down out from under one. Releases every permit back afterward, so this store is still usable (a later call simply re-establishes whichever sessions it needs) rather than left permanently unusable.
- async read(path, offset=0, length=None)¶
- async size(path)¶
- async exists(path)¶
- async listdir(path)¶