-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathewouldblock_engine.cc
More file actions
1880 lines (1655 loc) · 75.3 KB
/
Copy pathewouldblock_engine.cc
File metadata and controls
1880 lines (1655 loc) · 75.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Copyright 2015 Couchbase, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* "ewouldblock_engine"
*
* The "ewouldblock_engine" allows one to test how memcached responds when the
* engine returns EWOULDBLOCK instead of the correct response.
*
* Motivation:
*
* The EWOULDBLOCK response code can be returned from a number of engine
* functions, and is used to indicate that the request could not be immediately
* fulfilled, and it "would block" if it tried to. The correct way for
* memcached to handle this (in general) is to suspend that request until it
* is later notified by the engine (via notify_io_complete()).
*
* However, engines typically return the correct response to requests
* immediately, only rarely (and from memcached's POV non-deterministically)
* returning EWOULDBLOCK. This makes testing of the code-paths handling
* EWOULDBLOCK tricky.
*
*
* Operation:
* This engine, when loaded by memcached proxies requests to a "real" engine.
* Depending on how it is configured, it can simply pass the request on to the
* real engine, or artificially return EWOULDBLOCK back to memcached.
*
* See the 'Modes' enum below for the possible modes for a connection. The mode
* can be selected by sending a `request_ewouldblock_ctl` command
* (opcode PROTOCOL_BINARY_CMD_EWOULDBLOCK_CTL).
*
* DCP:
* There is a special DCP stream named "ewb_internal" which is an
* endless stream of items. You may also add a number at the end
* e.g. "ewb_internal:10" and it'll create a stream with 10 entries.
* It will always send the same K-V pair.
* Note that we don't register for disconnect events so you might
* experience weirdness if you first try to use the internal dcp
* stream, and then later on want to use the one provided by the
* engine. The workaround for that is to delete the bucket
* in between ;-) (put them in separate test suites and it'll all
* be handled for you.
*
* Any other stream name results in proxying the dcp request to
* the underlying engine's DCP implementation.
*
*/
#include "ewouldblock_engine.h"
#include <atomic>
#include <condition_variable>
#include <cstring>
#include <iostream>
#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <random>
#include <sstream>
#include <string>
#include <memcached/engine.h>
#include <memcached/extension.h>
#include <platform/cb_malloc.h>
#include <platform/dirutils.h>
#include <platform/thread.h>
#include <xattr/blob.h>
#include "utilities/engine_loader.h"
/* Public API declaration ****************************************************/
extern "C" {
MEMCACHED_PUBLIC_API
ENGINE_ERROR_CODE create_instance(uint64_t interface, GET_SERVER_API gsa,
ENGINE_HANDLE **handle);
MEMCACHED_PUBLIC_API
void destroy_engine(void);
}
class EWB_Engine;
// Mapping from wrapped handle to EWB handles.
static std::map<ENGINE_HANDLE*, EWB_Engine*> engine_map;
class NotificationThread : public Couchbase::Thread {
public:
NotificationThread(EWB_Engine& engine_)
: Thread("ewb:pendingQ"),
engine(engine_) {}
protected:
virtual void run() override;
protected:
EWB_Engine& engine;
};
/**
* The BlockMonitorThread represents the thread that is
* monitoring the "lock" file. Once the file is no longer
* there it will resume the client specified with the given
* id.
*/
class BlockMonitorThread : public Couchbase::Thread {
public:
BlockMonitorThread(EWB_Engine& engine_,
uint32_t id_,
const std::string file_)
: Thread("ewb:BlockMon"),
engine(engine_),
id(id_),
file(file_) {}
/**
* Wait for the underlying thread to reach the zombie state
* (== terminated, but not reaped)
*/
~BlockMonitorThread() {
waitForState(Couchbase::ThreadState::Zombie);
}
protected:
virtual void run() override;
private:
EWB_Engine& engine;
const uint32_t id;
const std::string file;
};
static void register_callback(ENGINE_HANDLE *, ENGINE_EVENT_TYPE,
EVENT_CALLBACK, const void *);
static SERVER_HANDLE_V1 wrapped_api;
static SERVER_HANDLE_V1 *real_api;
static void init_wrapped_api(GET_SERVER_API fn) {
static bool init = false;
if (init) {
return;
}
init = true;
real_api = fn();
wrapped_api = *real_api;
// Overrides
static SERVER_CALLBACK_API callback = *wrapped_api.callback;
callback.register_callback = register_callback;
wrapped_api.callback = &callback;
}
static SERVER_HANDLE_V1 *get_wrapped_gsa() {
return &wrapped_api;
}
/** ewouldblock_engine class */
class EWB_Engine : public ENGINE_HANDLE_V1 {
private:
enum class Cmd { NONE, GET_INFO, ALLOCATE, REMOVE, GET, STORE,
CAS, ARITHMETIC, LOCK, UNLOCK,
FLUSH, GET_STATS, UNKNOWN_COMMAND };
const char* to_string(Cmd cmd);
uint64_t (*get_connection_id)(const void* cookie);
public:
EWB_Engine(GET_SERVER_API gsa_);
~EWB_Engine();
// Convert from a handle back to the read object.
static EWB_Engine* to_engine(ENGINE_HANDLE* handle) {
return reinterpret_cast<EWB_Engine*> (handle);
}
/* Returns true if the next command should have a fake error code injected.
* @param func Address of the command function (get, store, etc).
* @param cookie The cookie for the user's request.
* @param[out] Error code to return.
*/
bool should_inject_error(Cmd cmd, const void* cookie,
ENGINE_ERROR_CODE& err) {
if (is_connection_suspended(cookie)) {
err = ENGINE_EWOULDBLOCK;
return true;
}
uint64_t id = get_connection_id(cookie);
std::lock_guard<std::mutex> guard(cookie_map_mutex);
auto iter = connection_map.find(id);
if (iter == connection_map.end()) {
return false;
}
const bool inject = iter->second.second->should_inject_error(cmd, err);
const bool add_to_pending_io_ops = iter->second.second->add_to_pending_io_ops();
if (inject) {
auto logger = gsa()->log->get_logger();
logger->log(EXTENSION_LOG_DEBUG, NULL,
"EWB_Engine: injecting error:%d for cmd:%s",
err, to_string(cmd));
if (err == ENGINE_EWOULDBLOCK && add_to_pending_io_ops) {
// The server expects that if EWOULDBLOCK is returned then the
// server should be notified in the future when the operation is
// ready - so add this op to the pending IO queue.
schedule_notification(iter->second.first);
}
}
return inject;
}
/* Implementation of all the engine functions. ***************************/
static const engine_info* get_info(ENGINE_HANDLE* handle) {
return &to_engine(handle)->info.eng_info;
}
static ENGINE_ERROR_CODE initialize(ENGINE_HANDLE* handle,
const char* config_str) {
EWB_Engine* ewb = to_engine(handle);
auto logger = ewb->gsa()->log->get_logger();
// Extract the name of the real engine we will be proxying; then
// create and initialize it.
std::string config(config_str);
auto seperator = config.find(";");
std::string real_engine_name(config.substr(0, seperator));
std::string real_engine_config;
if (seperator != std::string::npos) {
real_engine_config = config.substr(seperator + 1);
}
if ((ewb->real_engine_ref = load_engine(real_engine_name.c_str(),
NULL, NULL, logger)) == NULL) {
logger->log(EXTENSION_LOG_WARNING, NULL,
"ERROR: EWB_Engine::initialize(): Failed to load real "
"engine '%s'", real_engine_name.c_str());
abort();
}
if (!create_engine_instance(ewb->real_engine_ref, get_wrapped_gsa, NULL,
&ewb->real_handle)) {
logger->log(EXTENSION_LOG_WARNING, NULL,
"ERROR: EWB_Engine::initialize(): Failed create "
"engine instance '%s'", real_engine_name.c_str());
abort();
}
if (ewb->real_handle->interface != 1) {
logger->log(EXTENSION_LOG_WARNING, NULL,
"ERROR: EWB_Engine::initialize(): Only support engine "
"with interface v1 - got v%" PRIu64 ".",
ewb->real_engine->interface.interface);
abort();
}
ewb->real_engine =
reinterpret_cast<ENGINE_HANDLE_V1*>(ewb->real_handle);
engine_map[ewb->real_handle] = ewb;
ENGINE_ERROR_CODE res = ewb->real_engine->initialize(
ewb->real_handle, real_engine_config.c_str());
if (res == ENGINE_SUCCESS) {
// For engine interface functions which cannot return EWOULDBLOCK,
// and we otherwise don't want to interpose, we can simply use the
// real_engine's functions directly.
ewb->ENGINE_HANDLE_V1::item_set_cas = ewb->real_engine->item_set_cas;
ewb->ENGINE_HANDLE_V1::set_item_info = ewb->real_engine->set_item_info;
}
return res;
}
static void destroy(ENGINE_HANDLE* handle, const bool force) {
EWB_Engine* ewb = to_engine(handle);
ewb->real_engine->destroy(ewb->real_handle, force);
delete ewb;
}
static ENGINE_ERROR_CODE allocate(ENGINE_HANDLE* handle, const void* cookie,
item **item, const DocKey& key,
const size_t nbytes, const int flags,
const rel_time_t exptime,
uint8_t datatype, uint16_t vbucket) {
EWB_Engine* ewb = to_engine(handle);
ENGINE_ERROR_CODE err = ENGINE_SUCCESS;
if (ewb->should_inject_error(Cmd::ALLOCATE, cookie, err)) {
return err;
} else {
return ewb->real_engine->allocate(ewb->real_handle, cookie, item,
key, nbytes, flags, exptime,
datatype, vbucket);
}
}
static std::pair<cb::unique_item_ptr, item_info> allocate_ex(ENGINE_HANDLE* handle,
const void* cookie,
const DocKey& key,
const size_t nbytes,
const size_t priv_nbytes,
const int flags,
const rel_time_t exptime,
uint8_t datatype,
uint16_t vbucket) {
EWB_Engine* ewb = to_engine(handle);
ENGINE_ERROR_CODE err = ENGINE_SUCCESS;
if (ewb->should_inject_error(Cmd::ALLOCATE, cookie, err)) {
throw cb::engine_error(cb::engine_errc(err), "ewb: injecting error");
} else {
return ewb->real_engine->allocate_ex(ewb->real_handle, cookie,
key, nbytes, priv_nbytes,
flags, exptime, datatype,
vbucket);
}
}
static ENGINE_ERROR_CODE remove(ENGINE_HANDLE* handle, const void* cookie,
const DocKey& key, uint64_t* cas,
uint16_t vbucket,
mutation_descr_t* mut_info) {
EWB_Engine* ewb = to_engine(handle);
ENGINE_ERROR_CODE err = ENGINE_SUCCESS;
if (ewb->should_inject_error(Cmd::REMOVE, cookie, err)) {
return err;
} else {
return ewb->real_engine->remove(ewb->real_handle, cookie, key, cas,
vbucket, mut_info);
}
}
static void release(ENGINE_HANDLE* handle, const void *cookie, item* item) {
EWB_Engine* ewb = to_engine(handle);
auto logger = ewb->gsa()->log->get_logger();
logger->log(EXTENSION_LOG_DEBUG, nullptr, "EWB_Engine: release");
if (item == &ewb->dcp_mutation_item) {
// Ignore the DCP mutation, we own it (and don't track
// refcounts on it).
} else {
return ewb->real_engine->release(ewb->real_handle, cookie, item);
}
}
static ENGINE_ERROR_CODE get(ENGINE_HANDLE* handle,
const void* cookie,
item** item,
const DocKey& key,
uint16_t vbucket,
DocStateFilter documentStateFilter) {
EWB_Engine* ewb = to_engine(handle);
ENGINE_ERROR_CODE err = ENGINE_SUCCESS;
if (ewb->should_inject_error(Cmd::GET, cookie, err)) {
return err;
} else {
return ewb->real_engine->get(ewb->real_handle,
cookie,
item,
key,
vbucket,
documentStateFilter);
}
}
static cb::EngineErrorItemPair get_if(ENGINE_HANDLE* handle,
const void* cookie,
const DocKey& key,
uint16_t vbucket,
std::function<bool(
const item_info&)> filter) {
EWB_Engine* ewb = to_engine(handle);
ENGINE_ERROR_CODE err = ENGINE_SUCCESS;
if (ewb->should_inject_error(Cmd::GET, cookie, err)) {
return std::make_pair(cb::engine_errc::would_block,
cb::unique_item_ptr{nullptr,
cb::ItemDeleter{handle}});
} else {
return ewb->real_engine->get_if(
ewb->real_handle, cookie, key, vbucket, filter);
}
}
static cb::EngineErrorItemPair get_and_touch(ENGINE_HANDLE* handle,
const void* cookie,
const DocKey& key,
uint16_t vbucket,
uint32_t exptime) {
EWB_Engine* ewb = to_engine(handle);
ENGINE_ERROR_CODE err = ENGINE_SUCCESS;
if (ewb->should_inject_error(Cmd::GET, cookie, err)) {
return std::make_pair(
cb::engine_errc::would_block,
cb::unique_item_ptr{nullptr, cb::ItemDeleter{handle}});
} else {
return ewb->real_engine->get_and_touch(
ewb->real_handle, cookie, key, vbucket, exptime);
}
}
static ENGINE_ERROR_CODE get_locked(ENGINE_HANDLE* handle,
const void* cookie,
item** item,
const DocKey& key,
uint16_t vbucket,
uint32_t lock_timeout) {
EWB_Engine* ewb = to_engine(handle);
ENGINE_ERROR_CODE err = ENGINE_SUCCESS;
if (ewb->should_inject_error(Cmd::LOCK, cookie, err)) {
return err;
} else {
return ewb->real_engine->get_locked(ewb->real_handle,
cookie, item, key,
vbucket, lock_timeout);
}
}
static ENGINE_ERROR_CODE unlock(ENGINE_HANDLE* handle,
const void* cookie,
const DocKey& key,
uint16_t vbucket,
uint64_t cas) {
EWB_Engine* ewb = to_engine(handle);
ENGINE_ERROR_CODE err = ENGINE_SUCCESS;
if (ewb->should_inject_error(Cmd::UNLOCK, cookie, err)) {
return err;
} else {
return ewb->real_engine->unlock(ewb->real_handle, cookie, key,
vbucket, cas);
}
}
static ENGINE_ERROR_CODE store(ENGINE_HANDLE* handle, const void *cookie,
item* item, uint64_t *cas,
ENGINE_STORE_OPERATION operation,
DocumentState document_state) {
EWB_Engine* ewb = to_engine(handle);
ENGINE_ERROR_CODE err = ENGINE_SUCCESS;
Cmd opcode = (operation == OPERATION_CAS) ? Cmd::CAS : Cmd::STORE;
if (ewb->should_inject_error(opcode, cookie, err)) {
return err;
} else {
return ewb->real_engine->store(ewb->real_handle, cookie, item, cas,
operation, document_state);
}
}
static ENGINE_ERROR_CODE flush(ENGINE_HANDLE* handle, const void* cookie) {
// Flush is a little different - it often returns EWOULDBLOCK, and
// notify_io_complete() just tells the server it can issue it's *next*
// command (i.e. no need to re-flush). Therefore just pass Flush
// straight through for now.
EWB_Engine* ewb = to_engine(handle);
return ewb->real_engine->flush(ewb->real_handle, cookie);
}
static ENGINE_ERROR_CODE get_stats(ENGINE_HANDLE* handle,
const void* cookie, const char* stat_key,
int nkey, ADD_STAT add_stat) {
EWB_Engine* ewb = to_engine(handle);
ENGINE_ERROR_CODE err = ENGINE_SUCCESS;
if (ewb->should_inject_error(Cmd::GET_STATS, cookie, err)) {
return err;
} else {
return ewb->real_engine->get_stats(ewb->real_handle, cookie, stat_key,
nkey, add_stat);
}
}
static void reset_stats(ENGINE_HANDLE* handle, const void* cookie) {
EWB_Engine* ewb = to_engine(handle);
return ewb->real_engine->reset_stats(ewb->real_handle, cookie);
}
/* Handle 'unknown_command'. In additional to wrapping calls to the
* underlying real engine, this is also used to configure
* ewouldblock_engine itself using he CMD_EWOULDBLOCK_CTL opcode.
*/
static ENGINE_ERROR_CODE unknown_command(ENGINE_HANDLE* handle,
const void* cookie,
protocol_binary_request_header *request,
ADD_RESPONSE response,
DocNamespace doc_namespace) {
EWB_Engine* ewb = to_engine(handle);
if (request->request.opcode == PROTOCOL_BINARY_CMD_EWOULDBLOCK_CTL) {
auto logger = ewb->gsa()->log->get_logger();
auto* req = reinterpret_cast<request_ewouldblock_ctl*>(request);
const EWBEngineMode mode = static_cast<EWBEngineMode>(ntohl(req->message.body.mode));
const uint32_t value = ntohl(req->message.body.value);
const ENGINE_ERROR_CODE injected_error =
static_cast<ENGINE_ERROR_CODE>(ntohl(req->message.body.inject_error));
const std::string key((char*)req->bytes + sizeof(req->bytes),
ntohs(req->message.header.request.keylen));
std::shared_ptr<FaultInjectMode> new_mode = nullptr;
// Validate mode, and construct new fault injector.
switch (mode) {
case EWBEngineMode::Next_N:
new_mode = std::make_shared<ErrOnNextN>(injected_error, value);
break;
case EWBEngineMode::Random:
new_mode = std::make_shared<ErrRandom>(injected_error, value);
break;
case EWBEngineMode::First:
new_mode = std::make_shared<ErrOnFirst>(injected_error);
break;
case EWBEngineMode::Sequence:
new_mode = std::make_shared<ErrSequence>(injected_error, value);
break;
case EWBEngineMode::No_Notify:
new_mode = std::make_shared<ErrOnNoNotify>(injected_error);
break;
case EWBEngineMode::CasMismatch:
new_mode = std::make_shared<CASMismatch>(value);
break;
case EWBEngineMode::IncrementClusterMapRevno:
ewb->clustermap_revno++;
response(nullptr, 0, nullptr, 0, nullptr, 0,
PROTOCOL_BINARY_RAW_BYTES,
PROTOCOL_BINARY_RESPONSE_SUCCESS, 0, cookie);
return ENGINE_SUCCESS;
case EWBEngineMode::BlockMonitorFile:
return ewb->handleBlockMonitorFile(cookie, value, key,
response);
case EWBEngineMode::Suspend:
return ewb->handleSuspend(cookie, value, response);
case EWBEngineMode::Resume:
return ewb->handleResume(cookie, value, response);
case EWBEngineMode::SetItemCas:
return ewb->setItemCas(cookie, key, value, response);
}
if (new_mode == nullptr) {
logger->log(EXTENSION_LOG_WARNING, NULL,
"EWB_Engine::unknown_command(): "
"Got unexpected mode=%d for EWOULDBLOCK_CTL, ",
mode);
response(nullptr, 0, nullptr, 0, nullptr, 0,
PROTOCOL_BINARY_RAW_BYTES,
PROTOCOL_BINARY_RESPONSE_EINVAL, /*cas*/0, cookie);
return ENGINE_FAILED;
} else {
try {
logger->log(EXTENSION_LOG_DEBUG, NULL,
"EWB_Engine::unknown_command(): Setting EWB mode to "
"%s for cookie %d", new_mode->to_string().c_str(),
cookie);
uint64_t id = ewb->get_connection_id(cookie);
{
std::lock_guard<std::mutex> guard(ewb->cookie_map_mutex);
ewb->connection_map.erase(id);
ewb->connection_map.emplace(id, std::make_pair(cookie, new_mode));
}
response(nullptr, 0, nullptr, 0, nullptr, 0,
PROTOCOL_BINARY_RAW_BYTES,
PROTOCOL_BINARY_RESPONSE_SUCCESS, /*cas*/0, cookie);
return ENGINE_SUCCESS;
} catch (std::bad_alloc&) {
return ENGINE_ENOMEM;
}
}
} else {
ENGINE_ERROR_CODE err = ENGINE_SUCCESS;
if (ewb->should_inject_error(Cmd::UNKNOWN_COMMAND, cookie, err)) {
return err;
} else {
return ewb->real_engine->unknown_command(ewb->real_handle, cookie,
request, response,
doc_namespace);
}
}
}
static void item_set_cas(ENGINE_HANDLE *handle, const void* cookie,
item* item, uint64_t cas) {
// Should never be called as ENGINE_HANDLE_V1::item_set_cas is updated
// to point to the real_engine once it is initialized. This function
//only exists so there is a non-NULL value for
// ENGINE_HANDLE_V1::item_set_cas initially to keep load_engine()
// happy.
abort();
}
static bool get_item_info(ENGINE_HANDLE *handle, const void *cookie,
const item* item, item_info *item_info) {
EWB_Engine* ewb = to_engine(handle);
auto logger = ewb->gsa()->log->get_logger();
logger->log(EXTENSION_LOG_DEBUG, nullptr, "EWB_Engine: get_item_info");
// This function cannot return EWOULDBLOCK - just chain to the real
// engine's function, unless it is a request for our special DCP item.
if (item == &ewb->dcp_mutation_item) {
item_info->cas = 0;
item_info->vbucket_uuid = 0;
item_info->seqno = 0;
item_info->exptime = 0;
item_info->nbytes = ewb->dcp_mutation_item.value.size();
item_info->flags = 0;
item_info->datatype = PROTOCOL_BINARY_DATATYPE_XATTR;
item_info->nkey = ewb->dcp_mutation_item.key.size();
item_info->key = ewb->dcp_mutation_item.key.c_str();
item_info->value[0].iov_base = &ewb->dcp_mutation_item.value[0];
item_info->value[0].iov_len = item_info->nbytes;
return true;
} else {
return ewb->real_engine->get_item_info(ewb->real_handle, cookie,
item, item_info);
}
}
static bool set_item_info(ENGINE_HANDLE *handle, const void *cookie,
item* item, const item_info *item_info) {
// Should never be called - set item_set_cas().
abort();
}
static ENGINE_ERROR_CODE get_engine_vb_map(ENGINE_HANDLE* handle,
const void * cookie,
engine_get_vb_map_cb callback) {
// Used to test NOT_MY_VBUCKET - just return a dummy config.
EWB_Engine* ewb = to_engine(handle);
auto logger = ewb->gsa()->log->get_logger();
logger->log(EXTENSION_LOG_DEBUG, NULL, "EWB_Engine::get_engine_vb_map");
std::string vbmap =
"{\"rev\":" + std::to_string(ewb->clustermap_revno.load()) + "}";
callback(cookie, vbmap.data(), vbmap.length());
return ENGINE_SUCCESS;
}
GET_SERVER_API gsa;
union {
engine_info eng_info;
char buffer[sizeof(engine_info) +
(sizeof(feature_info) * LAST_REGISTERED_ENGINE_FEATURE)];
} info;
// Actual engine we are proxying requests to.
ENGINE_HANDLE* real_handle;
ENGINE_HANDLE_V1* real_engine;
engine_reference* real_engine_ref;
std::atomic_int clustermap_revno;
/**
* The method responsible for pushing all of the notify_io_complete
* to the frontend. It is run by notify_io_thread and not intended to
* be called by anyone else!.
*/
void process_notifications();
std::unique_ptr<Couchbase::Thread> notify_io_thread;
protected:
/**
* Handle the control message for block monitor file
*
* @param cookie The cookie executing the operation
* @param id The identifier used to represent the cookie
* @param file The file to monitor
* @param response callback used to send a response to the client
* @return The standard engine error codes
*/
ENGINE_ERROR_CODE handleBlockMonitorFile(const void* cookie,
uint32_t id,
const std::string& file,
ADD_RESPONSE response);
/**
* Handle the control message for suspend
*
* @param cookie The cookie executing the operation
* @param id The identifier used to represent the cookie to resume
* (the use of a different id is to allow resume to
* be sent on a different connection)
* @param response callback used to send a response to the client
* @return The standard engine error codes
*/
ENGINE_ERROR_CODE handleSuspend(const void* cookie,
uint32_t id,
ADD_RESPONSE response);
/**
* Handle the control message for resume
*
* @param cookie The cookie executing the operation
* @param id The identifier representing the connection to resume
* @param response callback used to send a response to the client
* @return The standard engine error codes
*/
ENGINE_ERROR_CODE handleResume(const void* cookie,
uint32_t id,
ADD_RESPONSE response);
/**
* @param cookie the cookie executing the operation
* @param key ID of the item whose CAS should be changed
* @param cas The new CAS
* @param response Response callback used to send a response to the client
* @return Standard engine error codes
*/
ENGINE_ERROR_CODE setItemCas(const void *cookie,
const std::string& key, uint32_t cas,
ADD_RESPONSE response);
private:
// Shared state between the main thread of execution and the background
// thread processing pending io ops.
std::mutex mutex;
std::condition_variable condvar;
std::queue<const void*> pending_io_ops;
std::atomic<bool> stop_notification_thread;
///////////////////////////////////////////////////////////////////////////
// All of the methods used in the DCP interface //
// //
// We don't support mocking with the DCP interface yet, so all access to //
// the DCP interface will be proxied down to the underlying engine. //
///////////////////////////////////////////////////////////////////////////
static ENGINE_ERROR_CODE dcp_step(ENGINE_HANDLE* handle, const void* cookie,
struct dcp_message_producers *producers);
static ENGINE_ERROR_CODE dcp_open(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
uint32_t seqno,
uint32_t flags,
cb::const_char_buffer name,
cb::const_byte_buffer json);
static ENGINE_ERROR_CODE dcp_add_stream(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
uint16_t vbucket,
uint32_t flags);
static ENGINE_ERROR_CODE dcp_close_stream(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
uint16_t vbucket);
static ENGINE_ERROR_CODE dcp_stream_req(ENGINE_HANDLE* handle,
const void* cookie, uint32_t flags,
uint32_t opaque, uint16_t vbucket,
uint64_t start_seqno,
uint64_t end_seqno,
uint64_t vbucket_uuid,
uint64_t snap_start_seqno,
uint64_t snap_end_seqno,
uint64_t *rollback_seqno,
dcp_add_failover_log callback);
static ENGINE_ERROR_CODE dcp_get_failover_log(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
uint16_t vbucket,
dcp_add_failover_log callback);
static ENGINE_ERROR_CODE dcp_stream_end(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
uint16_t vbucket,
uint32_t flags);
static ENGINE_ERROR_CODE dcp_snapshot_marker(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
uint16_t vbucket,
uint64_t start_seqno,
uint64_t end_seqno,
uint32_t flags);
static ENGINE_ERROR_CODE dcp_mutation(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
const DocKey& key,
cb::const_byte_buffer value,
size_t priv_bytes,
uint8_t datatype,
uint64_t cas,
uint16_t vbucket,
uint32_t flags,
uint64_t by_seqno,
uint64_t rev_seqno,
uint32_t expiration,
uint32_t lock_time,
cb::const_byte_buffer meta,
uint8_t nru);
static ENGINE_ERROR_CODE dcp_deletion(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
const DocKey& key,
cb::const_byte_buffer value,
size_t priv_bytes,
uint8_t datatype,
uint64_t cas,
uint16_t vbucket,
uint64_t by_seqno,
uint64_t rev_seqno,
cb::const_byte_buffer meta);
static ENGINE_ERROR_CODE dcp_expiration(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
const DocKey& key,
cb::const_byte_buffer value,
size_t priv_bytes,
uint8_t datatype,
uint64_t cas,
uint16_t vbucket,
uint64_t by_seqno,
uint64_t rev_seqno,
cb::const_byte_buffer meta);
static ENGINE_ERROR_CODE dcp_flush(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
uint16_t vbucket);
static ENGINE_ERROR_CODE dcp_set_vbucket_state(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
uint16_t vbucket,
vbucket_state_t state);
static ENGINE_ERROR_CODE dcp_noop(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque);
static ENGINE_ERROR_CODE dcp_buffer_acknowledgement(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
uint16_t vbucket,
uint32_t buffer_bytes);
static ENGINE_ERROR_CODE dcp_control(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
const void* key,
uint16_t nkey,
const void* value,
uint32_t nvalue);
static ENGINE_ERROR_CODE dcp_response_handler(ENGINE_HANDLE* handle,
const void* cookie,
protocol_binary_response_header* response);
static ENGINE_ERROR_CODE dcp_system_event(ENGINE_HANDLE* handle,
const void* cookie,
uint32_t opaque,
uint16_t vbucket,
uint32_t event,
uint64_t bySeqno,
cb::const_byte_buffer key,
cb::const_byte_buffer eventData);
static cb::engine_error collections_set_manifest(
ENGINE_HANDLE* handle, cb::const_char_buffer json);
// Base class for all fault injection modes.
struct FaultInjectMode {
FaultInjectMode(ENGINE_ERROR_CODE injected_error_)
: injected_error(injected_error_) {}
virtual bool add_to_pending_io_ops() {
return true;
}
virtual bool should_inject_error(Cmd cmd, ENGINE_ERROR_CODE& err) = 0;
virtual std::string to_string() const = 0;
protected:
ENGINE_ERROR_CODE injected_error;
};
// Subclasses for each fault inject mode: /////////////////////////////////
class ErrOnFirst : public FaultInjectMode {
public:
ErrOnFirst(ENGINE_ERROR_CODE injected_error_)
: FaultInjectMode(injected_error_),
prev_cmd(Cmd::NONE) {}
bool should_inject_error(Cmd cmd, ENGINE_ERROR_CODE& err) {
// Block unless the previous command from this cookie
// was the same - i.e. all of a connections' commands
// will EWOULDBLOCK the first time they are called.
bool inject = (prev_cmd != cmd);
prev_cmd = cmd;
if (inject) {
err = injected_error;
}
return inject;
}
std::string to_string() const {
return "ErrOnFirst inject_error=" + std::to_string(injected_error);
}
private:
// Last command issued by this cookie.
Cmd prev_cmd;
};
class ErrOnNextN : public FaultInjectMode {
public:
ErrOnNextN(ENGINE_ERROR_CODE injected_error_, uint32_t count_)
: FaultInjectMode(injected_error_),
count(count_) {}
bool should_inject_error(Cmd cmd, ENGINE_ERROR_CODE& err) {
if (count > 0) {
--count;
err = injected_error;
return true;
} else {
return false;
}
}
std::string to_string() const {
return std::string("ErrOnNextN") +
" inject_error=" + std::to_string(injected_error) +
" count=" + std::to_string(count);
}
private:
// The count of commands issued that should return error.
uint32_t count;
};
class ErrRandom : public FaultInjectMode {
public:
ErrRandom(ENGINE_ERROR_CODE injected_error_, uint32_t percentage_)
: FaultInjectMode(injected_error_),
percentage_to_err(percentage_) {}
bool should_inject_error(Cmd cmd, ENGINE_ERROR_CODE& err) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<uint32_t> dis(1, 100);
if (dis(gen) < percentage_to_err) {
err = injected_error;
return true;
} else {
return false;
}
}
std::string to_string() const {
return std::string("ErrRandom") +
" inject_error=" + std::to_string(injected_error) +
" percentage=" + std::to_string(percentage_to_err);
}
private: