Commit de0f2ab
authored
Add a flag to returns the CAS calue from ADD/SET/REPLACE/CAS and multi-set operations (#261)
* Warn about the CAS-plus-replication hazard on ReplicatingClient
ReplicatingClient's CAS-touching methods have always had a silent
correctness problem when used against more than one replica: each
server maintains its own CAS counter, so a CAS value cannot match on
more than one replica. any(returns) then reports success as long as
one server accepted the write, but the other replicas silently
rejected it, leaving them divergent.
The get-side methods are an equally potent footgun -- gets(),
get(get_cas=True), and get_multi(get_cas=True) return a CAS from
whichever replica happened to respond first, even though that value
cannot be safely passed back to cas() on a multi-replica client.
Add docstring warnings on the class and on each affected method, plus
a runtime UserWarning fired by a small _warn_multi_replica_cas helper
on the five at-risk surfaces (cas, set_multi with tuple-CAS keys,
gets, get(get_cas=True), get_multi(get_cas=True)) when the client has
more than one server. The warning is emitted via the warnings module
rather than the existing logger, since it's an API-misuse signal
(deduped by default, surfaced without logging configuration) rather
than an operational event.
For backwards compatibility, the behavior itself is left unchanged --
callers in single-replica deployments are unaffected.
* Return CAS from single-key mutators via `get_cas` kwarg
add(), set(), replace(), and cas() all produce an item with a new CAS
value on success, and the memcached binary protocol already returns it
in the response header -- the client was simply discarding it. Callers
who want to chain a CAS-guarded update after a write had to follow up
with a separate gets() round-trip, which is both slower and racy
(another writer could slip in between).
Add an optional `get_cas=False` kwarg matching the existing convention
on get()/get_multi(). When True, these methods now return a tuple of
`(success, cas)` instead of a plain bool; `cas` is the new CAS on
success, or None on failure.
For ReplicatingClient, get_cas=True is rejected with NotImplementedError
when the client is configured with more than one server, since each
replica has its own CAS counter and any value we returned would be
unsafe to feed back to cas(). Single-server ReplicatingClient (and
DistributedClient, which routes each key to exactly one server) work
normally.
* Add set_multi_cas for per-key CAS return from batched writes
set_multi's current return shape is a list of failed keys, which can't
carry a per-key CAS value. It also uses the quiet setq/addq opcodes,
which intentionally suppress successful responses -- so even if the
shape allowed it, the wire protocol wouldn't return a CAS per key.
Add a separate `set_multi_cas` method that uses the non-quiet set/add
opcodes (one response per key) and returns `{str_key: int | None}` for
every input key -- int on success, None on failure. The existing
`{(key, cas): value}` input syntax from set_multi is preserved; the
result dict is keyed by the string key regardless of which form was
passed.
For ReplicatingClient, set_multi_cas raises NotImplementedError when
the client has more than one server, for the same reason as the
single-key get_cas=True surfaces: each replica has its own CAS
counter, so any per-key value we returned would be unsafe to feed
back to cas(). Single-server ReplicatingClient and DistributedClient
work normally.1 parent b40f7fd commit de0f2ab
5 files changed
Lines changed: 571 additions & 59 deletions
File tree
- bmemcached
- client
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
42 | | - | |
| 42 | + | |
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
56 | | - | |
57 | | - | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
58 | 62 | | |
59 | 63 | | |
60 | | - | |
| 64 | + | |
61 | 65 | | |
62 | 66 | | |
63 | 67 | | |
| |||
86 | 90 | | |
87 | 91 | | |
88 | 92 | | |
89 | | - | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
90 | 124 | | |
91 | 125 | | |
92 | 126 | | |
| |||
100 | 134 | | |
101 | 135 | | |
102 | 136 | | |
103 | | - | |
104 | | - | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
105 | 143 | | |
106 | 144 | | |
107 | | - | |
| 145 | + | |
108 | 146 | | |
109 | | - | |
| 147 | + | |
110 | 148 | | |
111 | 149 | | |
112 | 150 | | |
| |||
120 | 158 | | |
121 | 159 | | |
122 | 160 | | |
123 | | - | |
124 | | - | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
125 | 167 | | |
126 | 168 | | |
127 | | - | |
| 169 | + | |
128 | 170 | | |
129 | 171 | | |
130 | 172 | | |
| |||
182 | 224 | | |
183 | 225 | | |
184 | 226 | | |
185 | | - | |
| 227 | + | |
186 | 228 | | |
187 | 229 | | |
188 | 230 | | |
| |||
198 | 240 | | |
199 | 241 | | |
200 | 242 | | |
201 | | - | |
202 | | - | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
203 | 249 | | |
204 | 250 | | |
205 | | - | |
| 251 | + | |
206 | 252 | | |
207 | 253 | | |
208 | 254 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
135 | | - | |
| 135 | + | |
136 | 136 | | |
137 | 137 | | |
138 | | - | |
| 138 | + | |
139 | 139 | | |
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
143 | 143 | | |
144 | | - | |
| 144 | + | |
145 | 145 | | |
146 | 146 | | |
147 | | - | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
148 | 151 | | |
149 | 152 | | |
150 | 153 | | |
| |||
0 commit comments