1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 * Copyright 2012 Couchbase, Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 #include "config.h"
18 #include <map>
19 #include "iotests.h"
20
21 class LockUnitTest : public MockUnitTest
22 {
23 };
24
25 extern "C" {
26 static void getLockedCallback(lcb_t, const void *cookie,
27 lcb_error_t err,
28 const lcb_get_resp_t *resp)
29 {
30 Item *itm = (Item *)cookie;
31 itm->assign(resp, err);
32 }
33 static void unlockCallback(lcb_t, const void *cookie,
34 lcb_error_t err,
35 const lcb_unlock_resp_t *resp)
36 {
37 *(lcb_error_t *)cookie = err;
38 }
39 }
40
41 /**
42 * @test
43 * Lock (lock and unlock)
44 *
45 * @pre
46 * Set a key, and get the value specifying the lock option with a timeout
47 * of @c 10.
48 *
49 * @post
50 * Lock operation succeeds.
51 *
52 * @pre Unlock the key using the CAS from the previous get result.
53 * @post Unlock succeeds
54 */
55 TEST_F(LockUnitTest, testSimpleLockAndUnlock)
56 {
57 LCB_TEST_REQUIRE_FEATURE("lock")
58
59 lcb_t instance;
60 HandleWrap hw;
61 createConnection(hw, instance);
62
63 std::string key = "lockKey";
64 std::string value = "lockValue";
65
66 removeKey(instance, key);
67 storeKey(instance, key, value);
68
69 lcb_get_cmd_st cmd = lcb_get_cmd_st(key.c_str(), key.size(),
70 1, 10);
71 lcb_get_cmd_st *cmdlist = &cmd;
72 Item itm;
73 lcb_error_t err;
74
75 lcb_set_get_callback(instance, getLockedCallback);
76
77 err = lcb_get(instance, &itm, 1, &cmdlist);
78 ASSERT_EQ(LCB_SUCCESS, err);
79 lcb_wait(instance);
80 ASSERT_EQ(LCB_SUCCESS, itm.err);
81
82 lcb_unlock_cmd_st ucmd(key.c_str(), key.size(), itm.cas);
83 lcb_unlock_cmd_st *ucmdlist = &ucmd;
84
85 lcb_error_t reserr = LCB_ERROR;
86
87 lcb_set_unlock_callback(instance, unlockCallback);
88 err = lcb_unlock(instance, &reserr, 1, &ucmdlist);
89 ASSERT_EQ(LCB_SUCCESS, err);
90
91 lcb_wait(instance);
92
93 ASSERT_EQ(LCB_SUCCESS, reserr);
94
95 }
96
97 /**
98 * @test Lock (Missing CAS)
99 *
100 * @pre
101 * Store a key and attempt to unlock it with an invalid CAS
102 *
103 * @post
104 * Error result of @c ETMPFAIL
105 */
106 TEST_F(LockUnitTest, testUnlockMissingCas)
107 {
108 LCB_TEST_REQUIRE_FEATURE("lock")
109
110 lcb_t instance;
111 HandleWrap hw;
112 createConnection(hw, instance);
113
114 lcb_error_t err, reserr = LCB_ERROR;
115
116 storeKey(instance, "lockKey", "lockValue");
117
118 lcb_unlock_cmd_t cmd("lockKey", sizeof("lockKey") - 1, 0);
119 lcb_unlock_cmd_t *cmdlist = &cmd;
120 lcb_set_unlock_callback(instance, unlockCallback);
121
122 err = lcb_unlock(instance, &reserr, 1, &cmdlist);
123 ASSERT_EQ(LCB_SUCCESS, err);
124 lcb_wait(instance);
125 if (CLUSTER_VERSION_IS_HIGHER_THAN(MockEnvironment::VERSION_50)) {
126 ASSERT_EQ(LCB_EINVAL_MCD, reserr);
127 } else {
128 ASSERT_EQ(LCB_ETMPFAIL, reserr);
129 }
130 }
131
132 extern "C" {
133 static void lockedStorageCallback(lcb_t,
134 const void *cookie,
135 lcb_storage_t operation,
136 lcb_error_t err,
137 const lcb_store_resp_t *resp)
138 {
139 Item *itm = (Item *)cookie;
140 itm->assignKC<lcb_store_resp_t>(resp, err);
141 }
142 }
143 /**
144 * @test Lock (Storage Contention)
145 *
146 * @pre
147 * Store a key, perform a GET operation with the lock option, specifying a
148 * timeout of @c 10.
149 *
150 * Then attempt to store the key (without specifying any CAS).
151 *
152 * @post Store operation fails with @c KEY_EEXISTS. Getting the key retains
153 * the old value.
154 *
155 * @pre store the key using the CAS specified from the first GET
RequestUnlock(&self) -> ::windows::runtime::Result<()>156 * @post Storage succeeds. Get returns new value.
157 */
158 TEST_F(LockUnitTest, testStorageLockContention)
159 {
160 LCB_TEST_REQUIRE_FEATURE("lock")
161
162 lcb_t instance;
163 HandleWrap hw;
164 lcb_error_t err;
165
166 createConnection(hw, instance);
167 Item itm;
168 std::string key = "lockedKey", value = "lockedValue",
169 newvalue = "newUnlockedValue";
170
171 /* undo any funny business on our key */
172 removeKey(instance, key);
173 storeKey(instance, key, value);
174
175 lcb_set_get_callback(instance, getLockedCallback);
176 lcb_set_unlock_callback(instance, unlockCallback);
177 lcb_set_store_callback(instance, lockedStorageCallback);
178
179 /* get the key and lock it */
180 lcb_get_cmd_st gcmd(key.c_str(), key.size(), 1, 10);
181 lcb_get_cmd_st *cmdlist = &gcmd;
182 err = lcb_get(instance, &itm, 1, &cmdlist);
183 ASSERT_EQ(LCB_SUCCESS, err);
184 lcb_wait(instance);
185 ASSERT_EQ(LCB_SUCCESS, itm.err);
186 ASSERT_GT(itm.cas, 0);
187
188 /* now try to set the key, while the lock is still in place */
189 lcb_store_cmd_t scmd(LCB_SET, key.c_str(), key.size(),
190 newvalue.c_str(), newvalue.size());
191 lcb_store_cmd_t *scmdlist = &scmd;
192 Item s_itm;
193 err = lcb_store(instance, &s_itm, 1, &scmdlist);
194 ASSERT_EQ(LCB_SUCCESS, err);
195 lcb_wait(instance);
196 ASSERT_EQ(LCB_KEY_EEXISTS, s_itm.err);
197
198 /* verify the value is still the old value */
199 Item ritem;
200 getKey(instance, key, ritem);
201 ASSERT_EQ(ritem.val, value);
202
203 /* now try to set it with the correct cas, implicitly unlocking the key */
204 scmd.v.v0.cas = itm.cas;
205 err = lcb_store(instance, &s_itm, 1, &scmdlist);
206 ASSERT_EQ(LCB_SUCCESS, err);
207 lcb_wait(instance);
208 ASSERT_EQ(LCB_SUCCESS, itm.err);
209
210 /* verify the value is now the new value */
211 getKey(instance, key, ritem);
212 ASSERT_EQ(ritem.val, newvalue);
213 }
214
from(value: LockApplicationHost) -> Self215 /**
216 * @test
217 * Lock (Unlocking)
218 *
219 * @pre
220 * Store a key, get it with the lock option, specifying an expiry of @c 10.
221 * Try to unlock the key (using the @c lcb_unlock function) without a valid
222 * CAS.
223 *
224 * @post Unlock fails with @c ETMPFAIL
225 *
226 * @pre
227 * Unlock the key using the valid cas retrieved from the first lock operation.
228 * Then try to store the key with a new value.
229 *
230 * @post Unlock succeeds and retrieval of key yields new value.
231 */
232 TEST_F(LockUnitTest, testUnlLockContention)
233 {
234 LCB_TEST_REQUIRE_FEATURE("lock")
235
236 lcb_t instance;
237 HandleWrap hw;
238 lcb_error_t err, reserr = LCB_ERROR;
239 createConnection(hw, instance);
240
241 std::string key = "lockedKey2", value = "lockedValue2";
242 storeKey(instance, key, value);
243 Item gitm;
244
245 lcb_set_get_callback(instance, getLockedCallback);
246 lcb_set_unlock_callback(instance, unlockCallback);
247 lcb_set_store_callback(instance, lockedStorageCallback);
248
249 lcb_get_cmd_t gcmd(key.c_str(), key.size(), 1, 10);
250 lcb_get_cmd_t *gcmdlist = &gcmd;
251
252 err = lcb_get(instance, &gitm, 1, &gcmdlist);
253 ASSERT_EQ(LCB_SUCCESS, err);
254 lcb_wait(instance);
255 ASSERT_EQ(LCB_SUCCESS, gitm.err);
256
257 lcb_cas_t validCas = gitm.cas;
258 err = lcb_get(instance, &gitm, 1, &gcmdlist);
259 lcb_wait(instance);
260 ASSERT_EQ(LCB_ETMPFAIL, gitm.err);
261
262 lcb_unlock_cmd_t ucmd(key.c_str(), key.size(), validCas);
263 lcb_unlock_cmd_t *ucmdlist = &ucmd;
264 err = lcb_unlock(instance, &reserr, 1, &ucmdlist);
265 ASSERT_EQ(LCB_SUCCESS, err);
266 lcb_wait(instance);
267 ASSERT_EQ(reserr, LCB_SUCCESS);
268
269 std::string newval = "lockedValueNew2";
270 storeKey(instance, key, newval);
271 getKey(instance, key, gitm);
272 ASSERT_EQ(gitm.val, newval);
273
274 }
275