forked from Expensify/Bedrock
-
Notifications
You must be signed in to change notification settings - Fork 0
/
BedrockCommand.h
177 lines (139 loc) · 7.71 KB
/
BedrockCommand.h
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
#pragma once
#include <libstuff/SHTTPSManager.h>
#include <sqlitecluster/SQLiteCommand.h>
class BedrockPlugin;
class BedrockCommand : public SQLiteCommand {
public:
enum Priority {
PRIORITY_MIN = 0,
PRIORITY_LOW = 250,
PRIORITY_NORMAL = 500,
PRIORITY_HIGH = 750,
PRIORITY_MAX = 1000
};
enum TIMING_INFO {
INVALID,
PEEK,
PROCESS,
COMMIT_WORKER,
COMMIT_SYNC,
QUEUE_WORKER,
QUEUE_SYNC,
};
enum class STAGE {
PEEK,
PROCESS
};
// Times in *milliseconds*.
static const uint64_t DEFAULT_TIMEOUT = 290'000; // 290 seconds, so clients can have a 5 minute timeout.
static const uint64_t DEFAULT_TIMEOUT_FORGET = 60'000 * 60; // 1 hour for `connection: forget` commands.
static const uint64_t DEFAULT_PROCESS_TIMEOUT = 30'000; // 30 seconds.
// Constructor to initialize via a request object (by move).
BedrockCommand(SQLiteCommand&& baseCommand, BedrockPlugin* plugin, bool escalateImmediately_ = false);
// Destructor.
virtual ~BedrockCommand();
// Called to attempt to handle a command in a read-only fashion. Should return true if the command has been
// completely handled and a response has been written into `command.response`, which can be returned to the client.
// Should return `false` if the command needs to write to the database or otherwise could not be finished in a
// read-only fashion (i.e., it opened an HTTPS request and is waiting for the response).
virtual bool peek(SQLite& db) { STHROW("430 Unrecognized command"); }
// Called after a command has returned `false` to peek, and will attempt to commit and distribute a transaction
// with any changes to the DB made by this plugin.
virtual void process(SQLite& db) { STHROW("500 Base class process called"); }
// Reset the command after a commit conflict. This is called both before `peek` and `process`. Typically, we don't
// want to reset anything in `process`, because we may have specifically stored values there in `peek` that we want
// to access later. However, we provide this functionality to allow commands that make HTTPS requests to handle
// this extra case, as we run `peek` and `process` as separate transactions for these commands.
// The base class version of this does *not* change anything with regards to HTTPS requests. These are preserved
// across `reset` calls.
virtual void reset(STAGE stage);
// Return the name of the plugin for this command.
const string& getName() const;
// Bedrock will call this before each `processCommand` (note: not `peekCommand`) for each plugin to allow it to
// enable query rewriting. If a plugin would like to enable query rewriting, this should return true, and it should
// set the rewriteHandler it would like to use.
virtual bool shouldEnableQueryRewriting(const SQLite& db, bool (**rewriteHandler)(int, const char*, string&)) {
return false;
}
// Start recording time for a given action type.
void startTiming(TIMING_INFO type);
// Finish recording time for a given action type. `type` must match what was passed to the most recent call to
// `startTiming`.
void stopTiming(TIMING_INFO type);
// Add a summary of our timing info to our response object.
void finalizeTimingInfo();
// Returns true if all of the httpsRequests for this command are complete (or if it has none).
bool areHttpsRequestsComplete() const;
// If the `peek` portion of this command needs to make an HTTPS request, this is where we store it.
list<SHTTPSManager::Transaction*> httpsRequests;
// Each command is assigned a priority.
Priority priority;
// We track how many times we `peek` and `process` each command.
int peekCount;
int processCount;
// A plugin can optionally handle a command for which the reply to the caller was undeliverable.
// Note that it gets no reference to the DB, this happens after the transaction is already complete.
virtual void handleFailedReply() {
// Default implementation does nothing.
}
// Set to true if we don't want to log timeout alerts, and let the caller deal with it.
virtual bool shouldSuppressTimeoutWarnings() { return false; }
// A command can set this to true to indicate it would like to have `peek` called again after completing a HTTPS
// request. This allows a single command to make multiple serial HTTPS requests. The command should clear this when
// all HTTPS requests are complete. It will be automatically cleared if the command throws an exception.
bool repeek;
// A list of timing sets, with an info type, start, and end.
list<tuple<TIMING_INFO, uint64_t, uint64_t>> timingInfo;
// This defaults to false, but a specific plugin can set it to 'true' to force this command to be passed
// to the sync thread for processing, thus guaranteeing that process() will not result in a conflict.
virtual bool onlyProcessOnSyncThread() { return false; }
// This is a set of name/value pairs that must be present and matching for two commands to compare as "equivalent"
// for the sake of determining whether they're likely to cause a crash.
// i.e., if this command has set this to {userID, reportList}, and the server crashes while processing this
// command, then any other command with the same methodLine, userID, and reportList will be flagged as likely to
// cause a crash, and not processed.
class CrashMap : public map<string, SString> {
public:
pair<CrashMap::iterator, bool> insert(const string& key) {
if (cmd.request.isSet(key)) {
return map<string, SString>::insert(make_pair(key, cmd.request.nameValueMap.at(key)));
}
return make_pair(end(), false);
}
private:
// We make BedrockCommand a friend so it can call our private constructors/assignment operators.
friend class BedrockCommand;
CrashMap(BedrockCommand& _cmd) : cmd(_cmd) { }
CrashMap(BedrockCommand& _cmd, CrashMap&& other) : map<string, SString>(move(other)), cmd(_cmd) { }
CrashMap& operator=(CrashMap&& other) {
map<string, SString>::operator=(move(other));
return *this;
}
// This is a reference to the command that created this object.
BedrockCommand& cmd;
};
CrashMap crashIdentifyingValues;
// Return the timestamp by which this command must finish executing.
uint64_t timeout() const { return _timeout; }
// Return the number of commands in existence.
static size_t getCommandCount() { return _commandCount.load(); }
// True if this command should be escalated immediately. This can be true for any command that does all of its work
// in `process` instead of peek, as it will always be escalated to leader
const bool escalateImmediately;
// Record the state we were acting under in the last call to `peek` or `process`.
SQLiteNode::State lastPeekedOrProcessedInState = SQLiteNode::UNKNOWN;
protected:
// The plugin that owns this command.
BedrockPlugin* _plugin;
private:
// Set certain initial state on construction. Common functionality to several constructors.
void _init();
// used as a temporary variable for startTiming and stopTiming.
tuple<TIMING_INFO, uint64_t, uint64_t> _inProgressTiming;
// Get the absolute timeout value for this command based on it's request. This is used to initialize _timeout.
static int64_t _getTimeout(const SData& request);
// This is a timestamp in *microseconds* for when this command should timeout.
uint64_t _timeout;
static atomic<size_t> _commandCount;
static const string defaultPluginName;
};