1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 #ifndef SYNC_DEBUGGER_H
4 #define SYNC_DEBUGGER_H
5 
6 #ifdef SYNCDEBUG
7 
8 #include <assert.h>
9 #include <deque>
10 #include <vector>
11 #include <boost/cstdint.hpp>
12 
13 /**
14  * @brief sync debugger class
15  *
16  * The sync debugger keeps track of the results of the last assignments of
17  * synced variables, and, if compiled with HAVE_BACKTRACE, it keeps a backtrace
18  * for every assignment. This allows communication between client and server
19  * to figure out which exact assignment (including backtrace) was the first
20  * assignment to differ between client and server.
21  */
22 class CSyncDebugger {
23 
24 	public:
25 
26 		/**
27 		 * @brief get sync debugger instance
28 		 *
29 		 * The sync debugger is a singleton (for now).
30 		 * @return the instance
31 		 */
32 		static CSyncDebugger* GetInstance();
33 
34 	private:
35 
36 		enum {
37 			MAX_STACK = 5,       ///< Maximum number of stackframes per HistItemWithBacktrace.
38 			BLOCK_SIZE = 2048,   ///< Number of \p HistItem per block history.
39 			HISTORY_SIZE = 2048, ///< Number of blocks of the entire history.
40 		};
41 
42 		/**
43 		 * @brief a clientside history item representing one assignment
44 		 *
45 		 * One clientside item in the history (without backtrace) is
46 		 * represented by this structure.
47 		 */
48 		struct HistItem {
49 			/// Checksum (XOR of 32 bit dwords of the data),
50 			/// or the data itself if it is 32 bits or less.
51 			unsigned data;
52 		};
53 
54 		/**
55 		 * @brief a serverside history item representing one assignment
56 		 *
57 		 * One serverside item in the history (with backtrace) is represented
58 		 * by this structure.
59 		 */
60 		struct HistItemWithBacktrace: public HistItem {
61 			const char* op;      ///< Pointer to short static string giving operator type (e.g. "+=").
62 			unsigned frameNum;   ///< gs->frameNum at the time this entry was committed.
63 			unsigned bt_size;    ///< Number of entries in the stacktrace.
64 			void* bt[MAX_STACK]; ///< The stacktrace (frame pointers).
65 		};
66 
67 	private:
68 
69 		// client thread
70 
71 		/**
72 		 * @brief the history on clients
73 		 *
74 		 * This points to the assignment history that doesn't have backtraces.
75 		 * It is used on platforms which don't have a backtrace() function
76 		 * (Windows) and on game clients. The game host uses historybt instead.
77 		 *
78 		 * Results of the last HISTORY_SIZE * BLOCK_SIZE = 2048 * 2048 = 4194304
79 		 * assignments to synced variables are stored in it.
80 		 */
81 		HistItem* history;
82 
83 		/**
84 		 * @brief the history on the host
85 		 *
86 		 * This points to the assignment history that does have backtraces.
87 		 * It is used when running as server on platforms that have a
88 		 * backtrace() function. Game clients use the history array instead.
89 		 *
90 		 * Results of the last 4194304 assignments to synced variables are
91 		 * stored in it, with a backtrace attached to each of these results.
92 		 */
93 		HistItemWithBacktrace* historybt;
94 
95 		unsigned historyIndex;         ///< Where are we in the history buffer?
96 		volatile bool disable_history; ///< Volatile because it is read by server thread and written by client thread.
97 		bool may_enable_history;       ///< Is it safe already to set disable_history = false?
98 		boost::uint64_t flop;          ///< Current (local) operation number.
99 
100 		// server thread
101 
102 		struct PlayerStruct
103 		{
104 			std::vector<unsigned> checksumResponses;
105 			boost::uint64_t remoteFlop;
106 			std::vector<unsigned> remoteHistory;
PlayerStructPlayerStruct107 			PlayerStruct() : remoteFlop(0) {}
108 		};
109 		typedef std::vector<PlayerStruct> PlayerVec;
110 		PlayerVec players;
111 		std::deque<unsigned> requestedBlocks;        ///< We are processing these blocks.
112 		std::deque<unsigned> pendingBlocksToRequest; ///< We still need to receive these blocks (slowly emptied).
113 		bool waitingForBlockResponse;                ///< Are we still waiting for a block response?
114 
115 	private:
116 
117 		// don't construct or copy
118 		CSyncDebugger();
119 		CSyncDebugger(const CSyncDebugger&);
120 		CSyncDebugger& operator=(const CSyncDebugger&);
121 		~CSyncDebugger();
122 
123 		/**
124 		 * @brief output a backtrace to the log
125 		 *
126 		 * Writes the backtrace attached to history item # index to the log.
127 		 * The backtrace is prefixed with prefix.
128 		 */
129 		void Backtrace(int index, const char* prefix) const;
130 		/**
131 		 * @brief get a checksum for a backtrace in the history
132 		 *
133 		 * @return a checksum for backtrace # index in the history.
134 		 */
135 		unsigned GetBacktraceChecksum(int index) const;
136 		/**
137 		 * @brief second step after desync
138 		 *
139 		 * Called by client to send a response to a checksum request.
140 		 */
141 		void ClientSendChecksumResponse();
142 		/**
143 		 * @brief third step after desync
144 		 *
145 		 * Called by server after all checksum responses have been received.
146 		 * Compares the checksumResponses and figures out which blocks are out
147 		 * of sync (have different checksum). For these blocks requests are
148 		 * queued which will be send next frames (one request at a time, see
149 		 * CSyncDebugger::ServerHandlePendingBlockRequests()).
150 		 */
151 		void ServerQueueBlockRequests();
152 		/**
153 		 * @brief fourth step after desync
154 		 *
155 		 * Called by client to send a response to a block request.
156 		 */
157 		void ClientSendBlockResponse(int block);
158 		/**
159 		 * @brief fifth step after desync
160 		 *
161 		 * Called each time a set of blockResponses (one for every client) is
162 		 * received.
163 		 * If there are no more pendingBlocksToRequest,
164 		 * it triggers the sixth step, ServerDumpStack().
165 		 */
166 		void ServerReceivedBlockResponses();
167 		/**
168 		 * @brief sixth step after desync
169 		 *
170 		 * Called by server once all blockResponses are received. It dumps a
171 		 * backtrace to the logger for every checksum mismatch in the block
172 		 * which was out of sync. The backtraces are passed to the logger in a
173 		 * fairly simple form consisting basically only of hexadecimal
174 		 * addresses. The logger class resolves those to function, file-name
175 		 * & line number.
176 		 */
177 		void ServerDumpStack();
178 
179 	public:
180 
181 		/**
182 		 * @brief the backbone of the sync debugger
183 		 *
184 		 * This function adds an item to the history and appends a backtrace and
185 		 * an operator (op) to it. p must point to the data to checksum and size
186 		 * must be the size of that data.
187 		 */
188 		void Sync(const void* p, unsigned size, const char* op);
189 
190 		/**
191 		 * @brief initialize
192 		 *
193 		 * Initialize the sync debugger.
194 		 * @param useBacktrace true for a server (requires approx. 160 MegaBytes
195 		 *   on 32 bit systems and 256 MegaBytes on 64 bit systems)
196 		 *   and false for a client (requires only 32 MegaBytes extra)
197 		 */
198 		void Initialize(bool useBacktrace, unsigned numPlayers);
199 		/**
200 		 * @brief first step after desync
201 		 *
202 		 * Does nothing
203 		 */
204 		void ServerTriggerSyncErrorHandling(int serverframenum);
205 		/**
206 		 * @brief serverside network receiver
207 		 *
208 		 * Plugin for the CGameServer network code in GameServer.cpp.
209 		 * @return the number of bytes read from the network stream
210 		 */
211 		bool ServerReceived(const unsigned char* inbuf);
212 		/**
213 		 * @brief helper for the third step
214 		 *
215 		 * Must be called by the server in GameServer.cpp once every frame to
216 		 * handle queued block requests.
217 		 * @see #ServerQueueBlockRequests()
218 		 */
219 		void ServerHandlePendingBlockRequests();
220 		/**
221 		 * @brief clientside network receiver
222 		 *
223 		 * Plugin for the CGame network code in Game.cpp.
224 		 * @return the number of bytes read from the network stream
225 		 */
226 		bool ClientReceived(const unsigned char* inbuf);
227 		/**
228 		 * @brief re-enable the history
229 		 *
230 		 * Restart the sync debugger lifecycle, so it can be used again (if the
231 		 * sync errors are resolved somehow or you were just testing it using
232 		 * /fakedesync).
233 		 *
234 		 * Called after typing '/reset' in chat area.
235 		 */
236 		void Reset();
237 
238 		friend class CSyncedPrimitiveBase;
239 };
240 
241 #endif // SYNCDEBUG
242 
243 #endif // SYNC_DEBUGGER_H
244