Show More
This diff has been collapsed as it changes many lines, (645 lines changed) Show them Hide them | |||
@@ -0,0 +1,645 b'' | |||
|
1 | $ . $TESTDIR/wireprotohelpers.sh | |
|
2 | $ cat >> $HGRCPATH << EOF | |
|
3 | > [extensions] | |
|
4 | > blackbox = | |
|
5 | > [blackbox] | |
|
6 | > track = simplecache | |
|
7 | > EOF | |
|
8 | $ hg init server | |
|
9 | $ enablehttpv2 server | |
|
10 | $ cd server | |
|
11 | $ cat >> .hg/hgrc << EOF | |
|
12 | > [extensions] | |
|
13 | > simplecache = $TESTDIR/wireprotosimplecache.py | |
|
14 | > EOF | |
|
15 | ||
|
16 | $ echo a0 > a | |
|
17 | $ echo b0 > b | |
|
18 | $ hg -q commit -A -m 'commit 0' | |
|
19 | $ echo a1 > a | |
|
20 | $ hg commit -m 'commit 1' | |
|
21 | $ echo b1 > b | |
|
22 | $ hg commit -m 'commit 2' | |
|
23 | $ echo a2 > a | |
|
24 | $ echo b2 > b | |
|
25 | $ hg commit -m 'commit 3' | |
|
26 | ||
|
27 | $ hg log -G -T '{rev}:{node} {desc}' | |
|
28 | @ 3:50590a86f3ff5d1e9a1624a7a6957884565cc8e8 commit 3 | |
|
29 | | | |
|
30 | o 2:4d01eda50c6ac5f7e89cbe1880143a32f559c302 commit 2 | |
|
31 | | | |
|
32 | o 1:4432d83626e8a98655f062ec1f2a43b07f7fbbb0 commit 1 | |
|
33 | | | |
|
34 | o 0:3390ef850073fbc2f0dfff2244342c8e9229013a commit 0 | |
|
35 | ||
|
36 | ||
|
37 | $ hg --debug debugindex -m | |
|
38 | rev linkrev nodeid p1 p2 | |
|
39 | 0 0 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 | |
|
40 | 1 1 a988fb43583e871d1ed5750ee074c6d840bbbfc8 992f4779029a3df8d0666d00bb924f69634e2641 0000000000000000000000000000000000000000 | |
|
41 | 2 2 a8853dafacfca6fc807055a660d8b835141a3bb4 a988fb43583e871d1ed5750ee074c6d840bbbfc8 0000000000000000000000000000000000000000 | |
|
42 | 3 3 3fe11dfbb13645782b0addafbe75a87c210ffddc a8853dafacfca6fc807055a660d8b835141a3bb4 0000000000000000000000000000000000000000 | |
|
43 | ||
|
44 | $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log | |
|
45 | $ cat hg.pid > $DAEMON_PIDS | |
|
46 | ||
|
47 | Performing the same request should result in same result, with 2nd response | |
|
48 | coming from cache. | |
|
49 | ||
|
50 | $ sendhttpv2peer << EOF | |
|
51 | > command manifestdata | |
|
52 | > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41'] | |
|
53 | > tree eval:b'' | |
|
54 | > fields eval:[b'parents'] | |
|
55 | > EOF | |
|
56 | creating http peer for wire protocol version 2 | |
|
57 | sending manifestdata command | |
|
58 | s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n | |
|
59 | s> Accept-Encoding: identity\r\n | |
|
60 | s> accept: application/mercurial-exp-framing-0005\r\n | |
|
61 | s> content-type: application/mercurial-exp-framing-0005\r\n | |
|
62 | s> content-length: 83\r\n | |
|
63 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
|
64 | s> user-agent: Mercurial debugwireproto\r\n | |
|
65 | s> \r\n | |
|
66 | s> K\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81GparentsEnodes\x81T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&ADtree@DnameLmanifestdata | |
|
67 | s> makefile('rb', None) | |
|
68 | s> HTTP/1.1 200 OK\r\n | |
|
69 | s> Server: testing stub value\r\n | |
|
70 | s> Date: $HTTP_DATE$\r\n | |
|
71 | s> Content-Type: application/mercurial-exp-framing-0005\r\n | |
|
72 | s> Transfer-Encoding: chunked\r\n | |
|
73 | s> \r\n | |
|
74 | s> 13\r\n | |
|
75 | s> \x0b\x00\x00\x01\x00\x02\x011 | |
|
76 | s> \xa1FstatusBok | |
|
77 | s> \r\n | |
|
78 | received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation) | |
|
79 | s> 63\r\n | |
|
80 | s> [\x00\x00\x01\x00\x02\x001 | |
|
81 | s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 | |
|
82 | s> \r\n | |
|
83 | received frame(size=91; request=1; stream=2; streamflags=; type=command-response; flags=continuation) | |
|
84 | s> 8\r\n | |
|
85 | s> \x00\x00\x00\x01\x00\x02\x002 | |
|
86 | s> \r\n | |
|
87 | s> 0\r\n | |
|
88 | s> \r\n | |
|
89 | received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) | |
|
90 | response: gen[ | |
|
91 | { | |
|
92 | b'totalitems': 1 | |
|
93 | }, | |
|
94 | { | |
|
95 | b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', | |
|
96 | b'parents': [ | |
|
97 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', | |
|
98 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |
|
99 | ] | |
|
100 | } | |
|
101 | ] | |
|
102 | ||
|
103 | $ sendhttpv2peer << EOF | |
|
104 | > command manifestdata | |
|
105 | > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41'] | |
|
106 | > tree eval:b'' | |
|
107 | > fields eval:[b'parents'] | |
|
108 | > EOF | |
|
109 | creating http peer for wire protocol version 2 | |
|
110 | sending manifestdata command | |
|
111 | s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n | |
|
112 | s> Accept-Encoding: identity\r\n | |
|
113 | s> accept: application/mercurial-exp-framing-0005\r\n | |
|
114 | s> content-type: application/mercurial-exp-framing-0005\r\n | |
|
115 | s> content-length: 83\r\n | |
|
116 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
|
117 | s> user-agent: Mercurial debugwireproto\r\n | |
|
118 | s> \r\n | |
|
119 | s> K\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81GparentsEnodes\x81T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&ADtree@DnameLmanifestdata | |
|
120 | s> makefile('rb', None) | |
|
121 | s> HTTP/1.1 200 OK\r\n | |
|
122 | s> Server: testing stub value\r\n | |
|
123 | s> Date: $HTTP_DATE$\r\n | |
|
124 | s> Content-Type: application/mercurial-exp-framing-0005\r\n | |
|
125 | s> Transfer-Encoding: chunked\r\n | |
|
126 | s> \r\n | |
|
127 | s> 13\r\n | |
|
128 | s> \x0b\x00\x00\x01\x00\x02\x011 | |
|
129 | s> \xa1FstatusBok | |
|
130 | s> \r\n | |
|
131 | received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation) | |
|
132 | s> 63\r\n | |
|
133 | s> [\x00\x00\x01\x00\x02\x001 | |
|
134 | s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 | |
|
135 | s> \r\n | |
|
136 | received frame(size=91; request=1; stream=2; streamflags=; type=command-response; flags=continuation) | |
|
137 | s> 8\r\n | |
|
138 | s> \x00\x00\x00\x01\x00\x02\x002 | |
|
139 | s> \r\n | |
|
140 | s> 0\r\n | |
|
141 | s> \r\n | |
|
142 | received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) | |
|
143 | response: gen[ | |
|
144 | { | |
|
145 | b'totalitems': 1 | |
|
146 | }, | |
|
147 | { | |
|
148 | b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', | |
|
149 | b'parents': [ | |
|
150 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', | |
|
151 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |
|
152 | ] | |
|
153 | } | |
|
154 | ] | |
|
155 | ||
|
156 | Sending different request doesn't yield cache hit. | |
|
157 | ||
|
158 | $ sendhttpv2peer << EOF | |
|
159 | > command manifestdata | |
|
160 | > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41', b'\xa9\x88\xfb\x43\x58\x3e\x87\x1d\x1e\xd5\x75\x0e\xe0\x74\xc6\xd8\x40\xbb\xbf\xc8'] | |
|
161 | > tree eval:b'' | |
|
162 | > fields eval:[b'parents'] | |
|
163 | > EOF | |
|
164 | creating http peer for wire protocol version 2 | |
|
165 | sending manifestdata command | |
|
166 | s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n | |
|
167 | s> Accept-Encoding: identity\r\n | |
|
168 | s> accept: application/mercurial-exp-framing-0005\r\n | |
|
169 | s> content-type: application/mercurial-exp-framing-0005\r\n | |
|
170 | s> content-length: 104\r\n | |
|
171 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
|
172 | s> user-agent: Mercurial debugwireproto\r\n | |
|
173 | s> \r\n | |
|
174 | s> `\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81GparentsEnodes\x82T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AT\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8Dtree@DnameLmanifestdata | |
|
175 | s> makefile('rb', None) | |
|
176 | s> HTTP/1.1 200 OK\r\n | |
|
177 | s> Server: testing stub value\r\n | |
|
178 | s> Date: $HTTP_DATE$\r\n | |
|
179 | s> Content-Type: application/mercurial-exp-framing-0005\r\n | |
|
180 | s> Transfer-Encoding: chunked\r\n | |
|
181 | s> \r\n | |
|
182 | s> 13\r\n | |
|
183 | s> \x0b\x00\x00\x01\x00\x02\x011 | |
|
184 | s> \xa1FstatusBok | |
|
185 | s> \r\n | |
|
186 | received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation) | |
|
187 | s> b1\r\n | |
|
188 | s> \xa9\x00\x00\x01\x00\x02\x001 | |
|
189 | s> \xa1Jtotalitems\x02\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa2DnodeT\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8Gparents\x82T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AT\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 | |
|
190 | s> \r\n | |
|
191 | received frame(size=169; request=1; stream=2; streamflags=; type=command-response; flags=continuation) | |
|
192 | s> 8\r\n | |
|
193 | s> \x00\x00\x00\x01\x00\x02\x002 | |
|
194 | s> \r\n | |
|
195 | s> 0\r\n | |
|
196 | s> \r\n | |
|
197 | received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) | |
|
198 | response: gen[ | |
|
199 | { | |
|
200 | b'totalitems': 2 | |
|
201 | }, | |
|
202 | { | |
|
203 | b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', | |
|
204 | b'parents': [ | |
|
205 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', | |
|
206 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |
|
207 | ] | |
|
208 | }, | |
|
209 | { | |
|
210 | b'node': b'\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8', | |
|
211 | b'parents': [ | |
|
212 | b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', | |
|
213 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |
|
214 | ] | |
|
215 | } | |
|
216 | ] | |
|
217 | ||
|
218 | $ cat .hg/blackbox.log | |
|
219 | *> cacher constructed for manifestdata (glob) | |
|
220 | *> cache miss for c045a581599d58608efd3d93d8129841f2af04a0 (glob) | |
|
221 | *> storing cache entry for c045a581599d58608efd3d93d8129841f2af04a0 (glob) | |
|
222 | *> cacher constructed for manifestdata (glob) | |
|
223 | *> cache hit for c045a581599d58608efd3d93d8129841f2af04a0 (glob) | |
|
224 | *> cacher constructed for manifestdata (glob) | |
|
225 | *> cache miss for 6ed2f740a1cdd12c9e99c4f27695543143c26a11 (glob) | |
|
226 | *> storing cache entry for 6ed2f740a1cdd12c9e99c4f27695543143c26a11 (glob) | |
|
227 | ||
|
228 | $ cat error.log | |
|
229 | ||
|
230 | $ killdaemons.py | |
|
231 | $ rm .hg/blackbox.log | |
|
232 | ||
|
233 | Try with object caching mode | |
|
234 | ||
|
235 | $ cat >> .hg/hgrc << EOF | |
|
236 | > [simplecache] | |
|
237 | > cacheobjects = true | |
|
238 | > EOF | |
|
239 | ||
|
240 | $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log | |
|
241 | $ cat hg.pid > $DAEMON_PIDS | |
|
242 | ||
|
243 | $ sendhttpv2peer << EOF | |
|
244 | > command manifestdata | |
|
245 | > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41'] | |
|
246 | > tree eval:b'' | |
|
247 | > fields eval:[b'parents'] | |
|
248 | > EOF | |
|
249 | creating http peer for wire protocol version 2 | |
|
250 | sending manifestdata command | |
|
251 | s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n | |
|
252 | s> Accept-Encoding: identity\r\n | |
|
253 | s> accept: application/mercurial-exp-framing-0005\r\n | |
|
254 | s> content-type: application/mercurial-exp-framing-0005\r\n | |
|
255 | s> content-length: 83\r\n | |
|
256 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
|
257 | s> user-agent: Mercurial debugwireproto\r\n | |
|
258 | s> \r\n | |
|
259 | s> K\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81GparentsEnodes\x81T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&ADtree@DnameLmanifestdata | |
|
260 | s> makefile('rb', None) | |
|
261 | s> HTTP/1.1 200 OK\r\n | |
|
262 | s> Server: testing stub value\r\n | |
|
263 | s> Date: $HTTP_DATE$\r\n | |
|
264 | s> Content-Type: application/mercurial-exp-framing-0005\r\n | |
|
265 | s> Transfer-Encoding: chunked\r\n | |
|
266 | s> \r\n | |
|
267 | s> 13\r\n | |
|
268 | s> \x0b\x00\x00\x01\x00\x02\x011 | |
|
269 | s> \xa1FstatusBok | |
|
270 | s> \r\n | |
|
271 | received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation) | |
|
272 | s> 63\r\n | |
|
273 | s> [\x00\x00\x01\x00\x02\x001 | |
|
274 | s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 | |
|
275 | s> \r\n | |
|
276 | received frame(size=91; request=1; stream=2; streamflags=; type=command-response; flags=continuation) | |
|
277 | s> 8\r\n | |
|
278 | s> \x00\x00\x00\x01\x00\x02\x002 | |
|
279 | s> \r\n | |
|
280 | s> 0\r\n | |
|
281 | s> \r\n | |
|
282 | received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) | |
|
283 | response: gen[ | |
|
284 | { | |
|
285 | b'totalitems': 1 | |
|
286 | }, | |
|
287 | { | |
|
288 | b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', | |
|
289 | b'parents': [ | |
|
290 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', | |
|
291 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |
|
292 | ] | |
|
293 | } | |
|
294 | ] | |
|
295 | ||
|
296 | $ sendhttpv2peer << EOF | |
|
297 | > command manifestdata | |
|
298 | > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41'] | |
|
299 | > tree eval:b'' | |
|
300 | > fields eval:[b'parents'] | |
|
301 | > EOF | |
|
302 | creating http peer for wire protocol version 2 | |
|
303 | sending manifestdata command | |
|
304 | s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n | |
|
305 | s> Accept-Encoding: identity\r\n | |
|
306 | s> accept: application/mercurial-exp-framing-0005\r\n | |
|
307 | s> content-type: application/mercurial-exp-framing-0005\r\n | |
|
308 | s> content-length: 83\r\n | |
|
309 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
|
310 | s> user-agent: Mercurial debugwireproto\r\n | |
|
311 | s> \r\n | |
|
312 | s> K\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81GparentsEnodes\x81T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&ADtree@DnameLmanifestdata | |
|
313 | s> makefile('rb', None) | |
|
314 | s> HTTP/1.1 200 OK\r\n | |
|
315 | s> Server: testing stub value\r\n | |
|
316 | s> Date: $HTTP_DATE$\r\n | |
|
317 | s> Content-Type: application/mercurial-exp-framing-0005\r\n | |
|
318 | s> Transfer-Encoding: chunked\r\n | |
|
319 | s> \r\n | |
|
320 | s> 13\r\n | |
|
321 | s> \x0b\x00\x00\x01\x00\x02\x011 | |
|
322 | s> \xa1FstatusBok | |
|
323 | s> \r\n | |
|
324 | received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation) | |
|
325 | s> 63\r\n | |
|
326 | s> [\x00\x00\x01\x00\x02\x001 | |
|
327 | s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 | |
|
328 | s> \r\n | |
|
329 | received frame(size=91; request=1; stream=2; streamflags=; type=command-response; flags=continuation) | |
|
330 | s> 8\r\n | |
|
331 | s> \x00\x00\x00\x01\x00\x02\x002 | |
|
332 | s> \r\n | |
|
333 | s> 0\r\n | |
|
334 | s> \r\n | |
|
335 | received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) | |
|
336 | response: gen[ | |
|
337 | { | |
|
338 | b'totalitems': 1 | |
|
339 | }, | |
|
340 | { | |
|
341 | b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A', | |
|
342 | b'parents': [ | |
|
343 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', | |
|
344 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |
|
345 | ] | |
|
346 | } | |
|
347 | ] | |
|
348 | ||
|
349 | $ cat .hg/blackbox.log | |
|
350 | *> cacher constructed for manifestdata (glob) | |
|
351 | *> cache miss for c045a581599d58608efd3d93d8129841f2af04a0 (glob) | |
|
352 | *> storing cache entry for c045a581599d58608efd3d93d8129841f2af04a0 (glob) | |
|
353 | *> cacher constructed for manifestdata (glob) | |
|
354 | *> cache hit for c045a581599d58608efd3d93d8129841f2af04a0 (glob) | |
|
355 | ||
|
356 | $ cat error.log | |
|
357 | ||
|
358 | $ killdaemons.py | |
|
359 | $ rm .hg/blackbox.log | |
|
360 | ||
|
361 | A non-cacheable command does not instantiate cacher | |
|
362 | ||
|
363 | $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log | |
|
364 | $ cat hg.pid > $DAEMON_PIDS | |
|
365 | $ sendhttpv2peer << EOF | |
|
366 | > command capabilities | |
|
367 | > EOF | |
|
368 | creating http peer for wire protocol version 2 | |
|
369 | sending capabilities command | |
|
370 | s> POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n | |
|
371 | s> Accept-Encoding: identity\r\n | |
|
372 | s> accept: application/mercurial-exp-framing-0005\r\n | |
|
373 | s> content-type: application/mercurial-exp-framing-0005\r\n | |
|
374 | s> content-length: 27\r\n | |
|
375 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
|
376 | s> user-agent: Mercurial debugwireproto\r\n | |
|
377 | s> \r\n | |
|
378 | s> \x13\x00\x00\x01\x00\x01\x01\x11\xa1DnameLcapabilities | |
|
379 | s> makefile('rb', None) | |
|
380 | s> HTTP/1.1 200 OK\r\n | |
|
381 | s> Server: testing stub value\r\n | |
|
382 | s> Date: $HTTP_DATE$\r\n | |
|
383 | s> Content-Type: application/mercurial-exp-framing-0005\r\n | |
|
384 | s> Transfer-Encoding: chunked\r\n | |
|
385 | s> \r\n | |
|
386 | s> 13\r\n | |
|
387 | s> \x0b\x00\x00\x01\x00\x02\x011 | |
|
388 | s> \xa1FstatusBok | |
|
389 | s> \r\n | |
|
390 | received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation) | |
|
391 | s> 52b\r\n | |
|
392 | s> #\x05\x00\x01\x00\x02\x001 | |
|
393 | s> \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x82\xa1DnameDzstd\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1 | |
|
394 | s> \r\n | |
|
395 | received frame(size=1315; request=1; stream=2; streamflags=; type=command-response; flags=continuation) | |
|
396 | s> 8\r\n | |
|
397 | s> \x00\x00\x00\x01\x00\x02\x002 | |
|
398 | s> \r\n | |
|
399 | s> 0\r\n | |
|
400 | s> \r\n | |
|
401 | received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos) | |
|
402 | response: gen[ | |
|
403 | { | |
|
404 | b'commands': { | |
|
405 | b'branchmap': { | |
|
406 | b'args': {}, | |
|
407 | b'permissions': [ | |
|
408 | b'pull' | |
|
409 | ] | |
|
410 | }, | |
|
411 | b'capabilities': { | |
|
412 | b'args': {}, | |
|
413 | b'permissions': [ | |
|
414 | b'pull' | |
|
415 | ] | |
|
416 | }, | |
|
417 | b'changesetdata': { | |
|
418 | b'args': { | |
|
419 | b'fields': { | |
|
420 | b'default': set([]), | |
|
421 | b'required': False, | |
|
422 | b'type': b'set', | |
|
423 | b'validvalues': set([ | |
|
424 | b'bookmarks', | |
|
425 | b'parents', | |
|
426 | b'phase', | |
|
427 | b'revision' | |
|
428 | ]) | |
|
429 | }, | |
|
430 | b'noderange': { | |
|
431 | b'default': None, | |
|
432 | b'required': False, | |
|
433 | b'type': b'list' | |
|
434 | }, | |
|
435 | b'nodes': { | |
|
436 | b'default': None, | |
|
437 | b'required': False, | |
|
438 | b'type': b'list' | |
|
439 | }, | |
|
440 | b'nodesdepth': { | |
|
441 | b'default': None, | |
|
442 | b'required': False, | |
|
443 | b'type': b'int' | |
|
444 | } | |
|
445 | }, | |
|
446 | b'permissions': [ | |
|
447 | b'pull' | |
|
448 | ] | |
|
449 | }, | |
|
450 | b'filedata': { | |
|
451 | b'args': { | |
|
452 | b'fields': { | |
|
453 | b'default': set([]), | |
|
454 | b'required': False, | |
|
455 | b'type': b'set', | |
|
456 | b'validvalues': set([ | |
|
457 | b'parents', | |
|
458 | b'revision' | |
|
459 | ]) | |
|
460 | }, | |
|
461 | b'haveparents': { | |
|
462 | b'default': False, | |
|
463 | b'required': False, | |
|
464 | b'type': b'bool' | |
|
465 | }, | |
|
466 | b'nodes': { | |
|
467 | b'required': True, | |
|
468 | b'type': b'list' | |
|
469 | }, | |
|
470 | b'path': { | |
|
471 | b'required': True, | |
|
472 | b'type': b'bytes' | |
|
473 | } | |
|
474 | }, | |
|
475 | b'permissions': [ | |
|
476 | b'pull' | |
|
477 | ] | |
|
478 | }, | |
|
479 | b'heads': { | |
|
480 | b'args': { | |
|
481 | b'publiconly': { | |
|
482 | b'default': False, | |
|
483 | b'required': False, | |
|
484 | b'type': b'bool' | |
|
485 | } | |
|
486 | }, | |
|
487 | b'permissions': [ | |
|
488 | b'pull' | |
|
489 | ] | |
|
490 | }, | |
|
491 | b'known': { | |
|
492 | b'args': { | |
|
493 | b'nodes': { | |
|
494 | b'default': [], | |
|
495 | b'required': False, | |
|
496 | b'type': b'list' | |
|
497 | } | |
|
498 | }, | |
|
499 | b'permissions': [ | |
|
500 | b'pull' | |
|
501 | ] | |
|
502 | }, | |
|
503 | b'listkeys': { | |
|
504 | b'args': { | |
|
505 | b'namespace': { | |
|
506 | b'required': True, | |
|
507 | b'type': b'bytes' | |
|
508 | } | |
|
509 | }, | |
|
510 | b'permissions': [ | |
|
511 | b'pull' | |
|
512 | ] | |
|
513 | }, | |
|
514 | b'lookup': { | |
|
515 | b'args': { | |
|
516 | b'key': { | |
|
517 | b'required': True, | |
|
518 | b'type': b'bytes' | |
|
519 | } | |
|
520 | }, | |
|
521 | b'permissions': [ | |
|
522 | b'pull' | |
|
523 | ] | |
|
524 | }, | |
|
525 | b'manifestdata': { | |
|
526 | b'args': { | |
|
527 | b'fields': { | |
|
528 | b'default': set([]), | |
|
529 | b'required': False, | |
|
530 | b'type': b'set', | |
|
531 | b'validvalues': set([ | |
|
532 | b'parents', | |
|
533 | b'revision' | |
|
534 | ]) | |
|
535 | }, | |
|
536 | b'haveparents': { | |
|
537 | b'default': False, | |
|
538 | b'required': False, | |
|
539 | b'type': b'bool' | |
|
540 | }, | |
|
541 | b'nodes': { | |
|
542 | b'required': True, | |
|
543 | b'type': b'list' | |
|
544 | }, | |
|
545 | b'tree': { | |
|
546 | b'required': True, | |
|
547 | b'type': b'bytes' | |
|
548 | } | |
|
549 | }, | |
|
550 | b'permissions': [ | |
|
551 | b'pull' | |
|
552 | ] | |
|
553 | }, | |
|
554 | b'pushkey': { | |
|
555 | b'args': { | |
|
556 | b'key': { | |
|
557 | b'required': True, | |
|
558 | b'type': b'bytes' | |
|
559 | }, | |
|
560 | b'namespace': { | |
|
561 | b'required': True, | |
|
562 | b'type': b'bytes' | |
|
563 | }, | |
|
564 | b'new': { | |
|
565 | b'required': True, | |
|
566 | b'type': b'bytes' | |
|
567 | }, | |
|
568 | b'old': { | |
|
569 | b'required': True, | |
|
570 | b'type': b'bytes' | |
|
571 | } | |
|
572 | }, | |
|
573 | b'permissions': [ | |
|
574 | b'push' | |
|
575 | ] | |
|
576 | } | |
|
577 | }, | |
|
578 | b'compression': [ | |
|
579 | { | |
|
580 | b'name': b'zstd' | |
|
581 | }, | |
|
582 | { | |
|
583 | b'name': b'zlib' | |
|
584 | } | |
|
585 | ], | |
|
586 | b'framingmediatypes': [ | |
|
587 | b'application/mercurial-exp-framing-0005' | |
|
588 | ], | |
|
589 | b'pathfilterprefixes': set([ | |
|
590 | b'path:', | |
|
591 | b'rootfilesin:' | |
|
592 | ]), | |
|
593 | b'rawrepoformats': [ | |
|
594 | b'generaldelta', | |
|
595 | b'revlogv1' | |
|
596 | ] | |
|
597 | } | |
|
598 | ] | |
|
599 | ||
|
600 | $ test -f .hg/blackbox.log | |
|
601 | [1] | |
|
602 | ||
|
603 | An error is not cached | |
|
604 | ||
|
605 | $ sendhttpv2peer << EOF | |
|
606 | > command manifestdata | |
|
607 | > nodes eval:[b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'] | |
|
608 | > tree eval:b'' | |
|
609 | > fields eval:[b'parents'] | |
|
610 | > EOF | |
|
611 | creating http peer for wire protocol version 2 | |
|
612 | sending manifestdata command | |
|
613 | s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n | |
|
614 | s> Accept-Encoding: identity\r\n | |
|
615 | s> accept: application/mercurial-exp-framing-0005\r\n | |
|
616 | s> content-type: application/mercurial-exp-framing-0005\r\n | |
|
617 | s> content-length: 83\r\n | |
|
618 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
|
619 | s> user-agent: Mercurial debugwireproto\r\n | |
|
620 | s> \r\n | |
|
621 | s> K\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81GparentsEnodes\x81T\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaaDtree@DnameLmanifestdata | |
|
622 | s> makefile('rb', None) | |
|
623 | s> HTTP/1.1 200 OK\r\n | |
|
624 | s> Server: testing stub value\r\n | |
|
625 | s> Date: $HTTP_DATE$\r\n | |
|
626 | s> Content-Type: application/mercurial-exp-framing-0005\r\n | |
|
627 | s> Transfer-Encoding: chunked\r\n | |
|
628 | s> \r\n | |
|
629 | s> 51\r\n | |
|
630 | s> I\x00\x00\x01\x00\x02\x012 | |
|
631 | s> \xa2Eerror\xa2Dargs\x81T\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaaGmessagePunknown node: %sFstatusEerror | |
|
632 | s> \r\n | |
|
633 | received frame(size=73; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos) | |
|
634 | s> 0\r\n | |
|
635 | s> \r\n | |
|
636 | abort: unknown node: \xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa! (esc) | |
|
637 | [255] | |
|
638 | ||
|
639 | $ cat .hg/blackbox.log | |
|
640 | *> cacher constructed for manifestdata (glob) | |
|
641 | *> cache miss for 9d1bb421d99e913d45f2d099aa49728514292dd2 (glob) | |
|
642 | *> cacher exiting due to error (glob) | |
|
643 | ||
|
644 | $ killdaemons.py | |
|
645 | $ rm .hg/blackbox.log |
@@ -0,0 +1,100 b'' | |||
|
1 | # wireprotosimplecache.py - Extension providing in-memory wire protocol cache | |
|
2 | # | |
|
3 | # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com> | |
|
4 | # | |
|
5 | # This software may be used and distributed according to the terms of the | |
|
6 | # GNU General Public License version 2 or any later version. | |
|
7 | ||
|
8 | from __future__ import absolute_import | |
|
9 | ||
|
10 | from mercurial import ( | |
|
11 | extensions, | |
|
12 | registrar, | |
|
13 | repository, | |
|
14 | util, | |
|
15 | wireprototypes, | |
|
16 | wireprotov2server, | |
|
17 | ) | |
|
18 | from mercurial.utils import ( | |
|
19 | interfaceutil, | |
|
20 | ) | |
|
21 | ||
|
22 | CACHE = None | |
|
23 | ||
|
24 | configtable = {} | |
|
25 | configitem = registrar.configitem(configtable) | |
|
26 | ||
|
27 | configitem('simplecache', 'cacheobjects', | |
|
28 | default=False) | |
|
29 | ||
|
30 | @interfaceutil.implementer(repository.iwireprotocolcommandcacher) | |
|
31 | class memorycacher(object): | |
|
32 | def __init__(self, ui, command, encodefn): | |
|
33 | self.ui = ui | |
|
34 | self.encodefn = encodefn | |
|
35 | self.key = None | |
|
36 | self.cacheobjects = ui.configbool('simplecache', 'cacheobjects') | |
|
37 | self.buffered = [] | |
|
38 | ||
|
39 | ui.log('simplecache', 'cacher constructed for %s\n', command) | |
|
40 | ||
|
41 | def __enter__(self): | |
|
42 | return self | |
|
43 | ||
|
44 | def __exit__(self, exctype, excvalue, exctb): | |
|
45 | if exctype: | |
|
46 | self.ui.log('simplecache', 'cacher exiting due to error\n') | |
|
47 | ||
|
48 | def adjustcachekeystate(self, state): | |
|
49 | # Needed in order to make tests deterministic. Don't copy this | |
|
50 | # pattern for production caches! | |
|
51 | del state[b'repo'] | |
|
52 | ||
|
53 | def setcachekey(self, key): | |
|
54 | self.key = key | |
|
55 | return True | |
|
56 | ||
|
57 | def lookup(self): | |
|
58 | if self.key not in CACHE: | |
|
59 | self.ui.log('simplecache', 'cache miss for %s\n', self.key) | |
|
60 | return None | |
|
61 | ||
|
62 | entry = CACHE[self.key] | |
|
63 | self.ui.log('simplecache', 'cache hit for %s\n', self.key) | |
|
64 | ||
|
65 | if self.cacheobjects: | |
|
66 | return { | |
|
67 | 'objs': entry, | |
|
68 | } | |
|
69 | else: | |
|
70 | return { | |
|
71 | 'objs': [wireprototypes.encodedresponse(entry)], | |
|
72 | } | |
|
73 | ||
|
74 | def onobject(self, obj): | |
|
75 | if self.cacheobjects: | |
|
76 | self.buffered.append(obj) | |
|
77 | else: | |
|
78 | self.buffered.extend(self.encodefn(obj)) | |
|
79 | ||
|
80 | yield obj | |
|
81 | ||
|
82 | def onfinished(self): | |
|
83 | self.ui.log('simplecache', 'storing cache entry for %s\n', self.key) | |
|
84 | if self.cacheobjects: | |
|
85 | CACHE[self.key] = self.buffered | |
|
86 | else: | |
|
87 | CACHE[self.key] = b''.join(self.buffered) | |
|
88 | ||
|
89 | return [] | |
|
90 | ||
|
91 | def makeresponsecacher(orig, repo, proto, command, args, objencoderfn): | |
|
92 | return memorycacher(repo.ui, command, objencoderfn) | |
|
93 | ||
|
94 | def extsetup(ui): | |
|
95 | global CACHE | |
|
96 | ||
|
97 | CACHE = util.lrucachedict(10000) | |
|
98 | ||
|
99 | extensions.wrapfunction(wireprotov2server, 'makeresponsecacher', | |
|
100 | makeresponsecacher) |
@@ -1657,3 +1657,160 b' class ilocalrepositorymain(interfaceutil' | |||
|
1657 | 1657 | class completelocalrepository(ilocalrepositorymain, |
|
1658 | 1658 | ilocalrepositoryfilestorage): |
|
1659 | 1659 | """Complete interface for a local repository.""" |
|
1660 | ||
|
1661 | class iwireprotocolcommandcacher(interfaceutil.Interface): | |
|
1662 | """Represents a caching backend for wire protocol commands. | |
|
1663 | ||
|
1664 | Wire protocol version 2 supports transparent caching of many commands. | |
|
1665 | To leverage this caching, servers can activate objects that cache | |
|
1666 | command responses. Objects handle both cache writing and reading. | |
|
1667 | This interface defines how that response caching mechanism works. | |
|
1668 | ||
|
1669 | Wire protocol version 2 commands emit a series of objects that are | |
|
1670 | serialized and sent to the client. The caching layer exists between | |
|
1671 | the invocation of the command function and the sending of its output | |
|
1672 | objects to an output layer. | |
|
1673 | ||
|
1674 | Instances of this interface represent a binding to a cache that | |
|
1675 | can serve a response (in place of calling a command function) and/or | |
|
1676 | write responses to a cache for subsequent use. | |
|
1677 | ||
|
1678 | When a command request arrives, the following happens with regards | |
|
1679 | to this interface: | |
|
1680 | ||
|
1681 | 1. The server determines whether the command request is cacheable. | |
|
1682 | 2. If it is, an instance of this interface is spawned. | |
|
1683 | 3. The cacher is activated in a context manager (``__enter__`` is called). | |
|
1684 | 4. A cache *key* for that request is derived. This will call the | |
|
1685 | instance's ``adjustcachekeystate()`` method so the derivation | |
|
1686 | can be influenced. | |
|
1687 | 5. The cacher is informed of the derived cache key via a call to | |
|
1688 | ``setcachekey()``. | |
|
1689 | 6. The cacher's ``lookup()`` method is called to test for presence of | |
|
1690 | the derived key in the cache. | |
|
1691 | 7. If ``lookup()`` returns a hit, that cached result is used in place | |
|
1692 | of invoking the command function. ``__exit__`` is called and the instance | |
|
1693 | is discarded. | |
|
1694 | 8. The command function is invoked. | |
|
1695 | 9. ``onobject()`` is called for each object emitted by the command | |
|
1696 | function. | |
|
1697 | 10. After the final object is seen, ``onoutputfinished()`` is called. | |
|
1698 | 11. ``__exit__`` is called to signal the end of use of the instance. | |
|
1699 | ||
|
1700 | Cache *key* derivation can be influenced by the instance. | |
|
1701 | ||
|
1702 | Cache keys are initially derived by a deterministic representation of | |
|
1703 | the command request. This includes the command name, arguments, protocol | |
|
1704 | version, etc. This initial key derivation is performed by CBOR-encoding a | |
|
1705 | data structure and feeding that output into a hasher. | |
|
1706 | ||
|
1707 | Instances of this interface can influence this initial key derivation | |
|
1708 | via ``adjustcachekeystate()``. | |
|
1709 | ||
|
1710 | The instance is informed of the derived cache key via a call to | |
|
1711 | ``setcachekey()``. The instance must store the key locally so it can | |
|
1712 | be consulted on subsequent operations that may require it. | |
|
1713 | ||
|
1714 | When constructed, the instance has access to a callable that can be used | |
|
1715 | for encoding response objects. This callable receives as its single | |
|
1716 | argument an object emitted by a command function. It returns an iterable | |
|
1717 | of bytes chunks representing the encoded object. Unless the cacher is | |
|
1718 | caching native Python objects in memory or has a way of reconstructing | |
|
1719 | the original Python objects, implementations typically call this function | |
|
1720 | to produce bytes from the output objects and then store those bytes in | |
|
1721 | the cache. When it comes time to re-emit those bytes, they are wrapped | |
|
1722 | in a ``wireprototypes.encodedresponse`` instance to tell the output | |
|
1723 | layer that they are pre-encoded. | |
|
1724 | ||
|
1725 | When receiving the objects emitted by the command function, instances | |
|
1726 | can choose what to do with those objects. The simplest thing to do is | |
|
1727 | re-emit the original objects. They will be forwarded to the output | |
|
1728 | layer and will be processed as if the cacher did not exist. | |
|
1729 | ||
|
1730 | Implementations could also choose to not emit objects - instead locally | |
|
1731 | buffering objects or their encoded representation. They could then emit | |
|
1732 | a single "coalesced" object when ``onoutputfinished()`` is called. In | |
|
1733 | this way, the implementation would function as a filtering layer of | |
|
1734 | sorts. | |
|
1735 | ||
|
1736 | When caching objects, typically the encoded form of the object will | |
|
1737 | be stored. Keep in mind that if the original object is forwarded to | |
|
1738 | the output layer, it will need to be encoded there as well. For large | |
|
1739 | output, this redundant encoding could add overhead. Implementations | |
|
1740 | could wrap the encoded object data in ``wireprototypes.encodedresponse`` | |
|
1741 | instances to avoid this overhead. | |
|
1742 | """ | |
|
1743 | def __enter__(): | |
|
1744 | """Marks the instance as active. | |
|
1745 | ||
|
1746 | Should return self. | |
|
1747 | """ | |
|
1748 | ||
|
1749 | def __exit__(exctype, excvalue, exctb): | |
|
1750 | """Called when cacher is no longer used. | |
|
1751 | ||
|
1752 | This can be used by implementations to perform cleanup actions (e.g. | |
|
1753 | disconnecting network sockets, aborting a partially cached response. | |
|
1754 | """ | |
|
1755 | ||
|
1756 | def adjustcachekeystate(state): | |
|
1757 | """Influences cache key derivation by adjusting state to derive key. | |
|
1758 | ||
|
1759 | A dict defining the state used to derive the cache key is passed. | |
|
1760 | ||
|
1761 | Implementations can modify this dict to record additional state that | |
|
1762 | is wanted to influence key derivation. | |
|
1763 | ||
|
1764 | Implementations are *highly* encouraged to not modify or delete | |
|
1765 | existing keys. | |
|
1766 | """ | |
|
1767 | ||
|
1768 | def setcachekey(key): | |
|
1769 | """Record the derived cache key for this request. | |
|
1770 | ||
|
1771 | Instances may mutate the key for internal usage, as desired. e.g. | |
|
1772 | instances may wish to prepend the repo name, introduce path | |
|
1773 | components for filesystem or URL addressing, etc. Behavior is up to | |
|
1774 | the cache. | |
|
1775 | ||
|
1776 | Returns a bool indicating if the request is cacheable by this | |
|
1777 | instance. | |
|
1778 | """ | |
|
1779 | ||
|
1780 | def lookup(): | |
|
1781 | """Attempt to resolve an entry in the cache. | |
|
1782 | ||
|
1783 | The instance is instructed to look for the cache key that it was | |
|
1784 | informed about via the call to ``setcachekey()``. | |
|
1785 | ||
|
1786 | If there's no cache hit or the cacher doesn't wish to use the cached | |
|
1787 | entry, ``None`` should be returned. | |
|
1788 | ||
|
1789 | Else, a dict defining the cached result should be returned. The | |
|
1790 | dict may have the following keys: | |
|
1791 | ||
|
1792 | objs | |
|
1793 | An iterable of objects that should be sent to the client. That | |
|
1794 | iterable of objects is expected to be what the command function | |
|
1795 | would return if invoked or an equivalent representation thereof. | |
|
1796 | """ | |
|
1797 | ||
|
1798 | def onobject(obj): | |
|
1799 | """Called when a new object is emitted from the command function. | |
|
1800 | ||
|
1801 | Receives as its argument the object that was emitted from the | |
|
1802 | command function. | |
|
1803 | ||
|
1804 | This method returns an iterator of objects to forward to the output | |
|
1805 | layer. The easiest implementation is a generator that just | |
|
1806 | ``yield obj``. | |
|
1807 | """ | |
|
1808 | ||
|
1809 | def onfinished(): | |
|
1810 | """Called after all objects have been emitted from the command function. | |
|
1811 | ||
|
1812 | Implementations should return an iterator of objects to forward to | |
|
1813 | the output layer. | |
|
1814 | ||
|
1815 | This method can be a generator. | |
|
1816 | """ |
@@ -232,11 +232,12 b' class baseprotocolhandler(interfaceutil.' | |||
|
232 | 232 | class commandentry(object): |
|
233 | 233 | """Represents a declared wire protocol command.""" |
|
234 | 234 | def __init__(self, func, args='', transports=None, |
|
235 | permission='push'): | |
|
235 | permission='push', cachekeyfn=None): | |
|
236 | 236 | self.func = func |
|
237 | 237 | self.args = args |
|
238 | 238 | self.transports = transports or set() |
|
239 | 239 | self.permission = permission |
|
240 | self.cachekeyfn = cachekeyfn | |
|
240 | 241 | |
|
241 | 242 | def _merge(self, func, args): |
|
242 | 243 | """Merge this instance with an incoming 2-tuple. |
@@ -7,6 +7,7 b'' | |||
|
7 | 7 | from __future__ import absolute_import |
|
8 | 8 | |
|
9 | 9 | import contextlib |
|
10 | import hashlib | |
|
10 | 11 | |
|
11 | 12 | from .i18n import _ |
|
12 | 13 | from .node import ( |
@@ -25,6 +26,7 b' from . import (' | |||
|
25 | 26 | wireprototypes, |
|
26 | 27 | ) |
|
27 | 28 | from .utils import ( |
|
29 | cborutil, | |
|
28 | 30 | interfaceutil, |
|
29 | 31 | stringutil, |
|
30 | 32 | ) |
@@ -35,6 +37,11 b' HTTP_WIREPROTO_V2 = wireprototypes.HTTP_' | |||
|
35 | 37 | |
|
36 | 38 | COMMANDS = wireprototypes.commanddict() |
|
37 | 39 | |
|
40 | # Value inserted into cache key computation function. Change the value to | |
|
41 | # force new cache keys for every command request. This should be done when | |
|
42 | # there is a change to how caching works, etc. | |
|
43 | GLOBAL_CACHE_VERSION = 1 | |
|
44 | ||
|
38 | 45 | def handlehttpv2request(rctx, req, res, checkperm, urlparts): |
|
39 | 46 | from .hgweb import common as hgwebcommon |
|
40 | 47 | |
@@ -333,12 +340,64 b' def getdispatchrepo(repo, proto, command' | |||
|
333 | 340 | return repo.filtered('served') |
|
334 | 341 | |
|
335 | 342 | def dispatch(repo, proto, command): |
|
343 | """Run a wire protocol command. | |
|
344 | ||
|
345 | Returns an iterable of objects that will be sent to the client. | |
|
346 | """ | |
|
336 | 347 | repo = getdispatchrepo(repo, proto, command) |
|
337 | 348 | |
|
338 |
|
|
|
349 | entry = COMMANDS[command] | |
|
350 | func = entry.func | |
|
351 | spec = entry.args | |
|
352 | ||
|
339 | 353 | args = proto.getargs(spec) |
|
340 | 354 | |
|
341 | return func(repo, proto, **pycompat.strkwargs(args)) | |
|
355 | # There is some duplicate boilerplate code here for calling the command and | |
|
356 | # emitting objects. It is either that or a lot of indented code that looks | |
|
357 | # like a pyramid (since there are a lot of code paths that result in not | |
|
358 | # using the cacher). | |
|
359 | callcommand = lambda: func(repo, proto, **pycompat.strkwargs(args)) | |
|
360 | ||
|
361 | # Request is not cacheable. Don't bother instantiating a cacher. | |
|
362 | if not entry.cachekeyfn: | |
|
363 | for o in callcommand(): | |
|
364 | yield o | |
|
365 | return | |
|
366 | ||
|
367 | cacher = makeresponsecacher(repo, proto, command, args, | |
|
368 | cborutil.streamencode) | |
|
369 | ||
|
370 | # But we have no cacher. Do default handling. | |
|
371 | if not cacher: | |
|
372 | for o in callcommand(): | |
|
373 | yield o | |
|
374 | return | |
|
375 | ||
|
376 | with cacher: | |
|
377 | cachekey = entry.cachekeyfn(repo, proto, cacher, **args) | |
|
378 | ||
|
379 | # No cache key or the cacher doesn't like it. Do default handling. | |
|
380 | if cachekey is None or not cacher.setcachekey(cachekey): | |
|
381 | for o in callcommand(): | |
|
382 | yield o | |
|
383 | return | |
|
384 | ||
|
385 | # Serve it from the cache, if possible. | |
|
386 | cached = cacher.lookup() | |
|
387 | ||
|
388 | if cached: | |
|
389 | for o in cached['objs']: | |
|
390 | yield o | |
|
391 | return | |
|
392 | ||
|
393 | # Else call the command and feed its output into the cacher, allowing | |
|
394 | # the cacher to buffer/mutate objects as it desires. | |
|
395 | for o in callcommand(): | |
|
396 | for o in cacher.onobject(o): | |
|
397 | yield o | |
|
398 | ||
|
399 | for o in cacher.onfinished(): | |
|
400 | yield o | |
|
342 | 401 | |
|
343 | 402 | @interfaceutil.implementer(wireprototypes.baseprotocolhandler) |
|
344 | 403 | class httpv2protocolhandler(object): |
@@ -460,7 +519,7 b' def _capabilitiesv2(repo, proto):' | |||
|
460 | 519 | |
|
461 | 520 | return proto.addcapabilities(repo, caps) |
|
462 | 521 | |
|
463 | def wireprotocommand(name, args=None, permission='push'): | |
|
522 | def wireprotocommand(name, args=None, permission='push', cachekeyfn=None): | |
|
464 | 523 | """Decorator to declare a wire protocol command. |
|
465 | 524 | |
|
466 | 525 | ``name`` is the name of the wire protocol command being provided. |
@@ -489,11 +548,21 b' def wireprotocommand(name, args=None, pe' | |||
|
489 | 548 | because otherwise commands not declaring their permissions could modify |
|
490 | 549 | a repository that is supposed to be read-only. |
|
491 | 550 | |
|
551 | ``cachekeyfn`` defines an optional callable that can derive the | |
|
552 | cache key for this request. | |
|
553 | ||
|
492 | 554 | Wire protocol commands are generators of objects to be serialized and |
|
493 | 555 | sent to the client. |
|
494 | 556 | |
|
495 | 557 | If a command raises an uncaught exception, this will be translated into |
|
496 | 558 | a command error. |
|
559 | ||
|
560 | All commands can opt in to being cacheable by defining a function | |
|
561 | (``cachekeyfn``) that is called to derive a cache key. This function | |
|
562 | receives the same arguments as the command itself plus a ``cacher`` | |
|
563 | argument containing the active cacher for the request and returns a bytes | |
|
564 | containing the key in a cache the response to this command may be cached | |
|
565 | under. | |
|
497 | 566 | """ |
|
498 | 567 | transports = {k for k, v in wireprototypes.TRANSPORTS.items() |
|
499 | 568 | if v['version'] == 2} |
@@ -543,12 +612,97 b' def wireprotocommand(name, args=None, pe' | |||
|
543 | 612 | 'for version 2' % name) |
|
544 | 613 | |
|
545 | 614 | COMMANDS[name] = wireprototypes.commandentry( |
|
546 |
func, args=args, transports=transports, permission=permission |
|
|
615 | func, args=args, transports=transports, permission=permission, | |
|
616 | cachekeyfn=cachekeyfn) | |
|
547 | 617 | |
|
548 | 618 | return func |
|
549 | 619 | |
|
550 | 620 | return register |
|
551 | 621 | |
|
622 | def makecommandcachekeyfn(command, localversion=None, allargs=False): | |
|
623 | """Construct a cache key derivation function with common features. | |
|
624 | ||
|
625 | By default, the cache key is a hash of: | |
|
626 | ||
|
627 | * The command name. | |
|
628 | * A global cache version number. | |
|
629 | * A local cache version number (passed via ``localversion``). | |
|
630 | * All the arguments passed to the command. | |
|
631 | * The media type used. | |
|
632 | * Wire protocol version string. | |
|
633 | * The repository path. | |
|
634 | """ | |
|
635 | if not allargs: | |
|
636 | raise error.ProgrammingError('only allargs=True is currently supported') | |
|
637 | ||
|
638 | if localversion is None: | |
|
639 | raise error.ProgrammingError('must set localversion argument value') | |
|
640 | ||
|
641 | def cachekeyfn(repo, proto, cacher, **args): | |
|
642 | spec = COMMANDS[command] | |
|
643 | ||
|
644 | # Commands that mutate the repo can not be cached. | |
|
645 | if spec.permission == 'push': | |
|
646 | return None | |
|
647 | ||
|
648 | # TODO config option to disable caching. | |
|
649 | ||
|
650 | # Our key derivation strategy is to construct a data structure | |
|
651 | # holding everything that could influence cacheability and to hash | |
|
652 | # the CBOR representation of that. Using CBOR seems like it might | |
|
653 | # be overkill. However, simpler hashing mechanisms are prone to | |
|
654 | # duplicate input issues. e.g. if you just concatenate two values, | |
|
655 | # "foo"+"bar" is identical to "fo"+"obar". Using CBOR provides | |
|
656 | # "padding" between values and prevents these problems. | |
|
657 | ||
|
658 | # Seed the hash with various data. | |
|
659 | state = { | |
|
660 | # To invalidate all cache keys. | |
|
661 | b'globalversion': GLOBAL_CACHE_VERSION, | |
|
662 | # More granular cache key invalidation. | |
|
663 | b'localversion': localversion, | |
|
664 | # Cache keys are segmented by command. | |
|
665 | b'command': pycompat.sysbytes(command), | |
|
666 | # Throw in the media type and API version strings so changes | |
|
667 | # to exchange semantics invalid cache. | |
|
668 | b'mediatype': FRAMINGTYPE, | |
|
669 | b'version': HTTP_WIREPROTO_V2, | |
|
670 | # So same requests for different repos don't share cache keys. | |
|
671 | b'repo': repo.root, | |
|
672 | } | |
|
673 | ||
|
674 | # The arguments passed to us will have already been normalized. | |
|
675 | # Default values will be set, etc. This is important because it | |
|
676 | # means that it doesn't matter if clients send an explicit argument | |
|
677 | # or rely on the default value: it will all normalize to the same | |
|
678 | # set of arguments on the server and therefore the same cache key. | |
|
679 | # | |
|
680 | # Arguments by their very nature must support being encoded to CBOR. | |
|
681 | # And the CBOR encoder is deterministic. So we hash the arguments | |
|
682 | # by feeding the CBOR of their representation into the hasher. | |
|
683 | if allargs: | |
|
684 | state[b'args'] = pycompat.byteskwargs(args) | |
|
685 | ||
|
686 | cacher.adjustcachekeystate(state) | |
|
687 | ||
|
688 | hasher = hashlib.sha1() | |
|
689 | for chunk in cborutil.streamencode(state): | |
|
690 | hasher.update(chunk) | |
|
691 | ||
|
692 | return pycompat.sysbytes(hasher.hexdigest()) | |
|
693 | ||
|
694 | return cachekeyfn | |
|
695 | ||
|
696 | def makeresponsecacher(repo, proto, command, args, objencoderfn): | |
|
697 | """Construct a cacher for a cacheable command. | |
|
698 | ||
|
699 | Returns an ``iwireprotocolcommandcacher`` instance. | |
|
700 | ||
|
701 | Extensions can monkeypatch this function to provide custom caching | |
|
702 | backends. | |
|
703 | """ | |
|
704 | return None | |
|
705 | ||
|
552 | 706 | @wireprotocommand('branchmap', permission='pull') |
|
553 | 707 | def branchmapv2(repo, proto): |
|
554 | 708 | yield {encoding.fromlocal(k): v |
@@ -755,7 +909,11 b' def getfilestore(repo, proto, path):' | |||
|
755 | 909 | 'example': b'foo.txt', |
|
756 | 910 | } |
|
757 | 911 | }, |
|
758 |
permission='pull' |
|
|
912 | permission='pull', | |
|
913 | # TODO censoring a file revision won't invalidate the cache. | |
|
914 | # Figure out a way to take censoring into account when deriving | |
|
915 | # the cache key. | |
|
916 | cachekeyfn=makecommandcachekeyfn('filedata', 1, allargs=True)) | |
|
759 | 917 | def filedata(repo, proto, haveparents, nodes, fields, path): |
|
760 | 918 | try: |
|
761 | 919 | # Extensions may wish to access the protocol handler. |
@@ -893,7 +1051,8 b' def lookupv2(repo, proto, key):' | |||
|
893 | 1051 | 'example': b'', |
|
894 | 1052 | }, |
|
895 | 1053 | }, |
|
896 |
permission='pull' |
|
|
1054 | permission='pull', | |
|
1055 | cachekeyfn=makecommandcachekeyfn('manifestdata', 1, allargs=True)) | |
|
897 | 1056 | def manifestdata(repo, proto, haveparents, nodes, fields, tree): |
|
898 | 1057 | store = repo.manifestlog.getstorage(tree) |
|
899 | 1058 |
General Comments 0
You need to be logged in to leave comments.
Login now