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 | class completelocalrepository(ilocalrepositorymain, |
|
1657 | class completelocalrepository(ilocalrepositorymain, | |
1658 | ilocalrepositoryfilestorage): |
|
1658 | ilocalrepositoryfilestorage): | |
1659 | """Complete interface for a local repository.""" |
|
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 | class commandentry(object): |
|
232 | class commandentry(object): | |
233 | """Represents a declared wire protocol command.""" |
|
233 | """Represents a declared wire protocol command.""" | |
234 | def __init__(self, func, args='', transports=None, |
|
234 | def __init__(self, func, args='', transports=None, | |
235 | permission='push'): |
|
235 | permission='push', cachekeyfn=None): | |
236 | self.func = func |
|
236 | self.func = func | |
237 | self.args = args |
|
237 | self.args = args | |
238 | self.transports = transports or set() |
|
238 | self.transports = transports or set() | |
239 | self.permission = permission |
|
239 | self.permission = permission | |
|
240 | self.cachekeyfn = cachekeyfn | |||
240 |
|
241 | |||
241 | def _merge(self, func, args): |
|
242 | def _merge(self, func, args): | |
242 | """Merge this instance with an incoming 2-tuple. |
|
243 | """Merge this instance with an incoming 2-tuple. |
@@ -7,6 +7,7 b'' | |||||
7 | from __future__ import absolute_import |
|
7 | from __future__ import absolute_import | |
8 |
|
8 | |||
9 | import contextlib |
|
9 | import contextlib | |
|
10 | import hashlib | |||
10 |
|
11 | |||
11 | from .i18n import _ |
|
12 | from .i18n import _ | |
12 | from .node import ( |
|
13 | from .node import ( | |
@@ -25,6 +26,7 b' from . import (' | |||||
25 | wireprototypes, |
|
26 | wireprototypes, | |
26 | ) |
|
27 | ) | |
27 | from .utils import ( |
|
28 | from .utils import ( | |
|
29 | cborutil, | |||
28 | interfaceutil, |
|
30 | interfaceutil, | |
29 | stringutil, |
|
31 | stringutil, | |
30 | ) |
|
32 | ) | |
@@ -35,6 +37,11 b' HTTP_WIREPROTO_V2 = wireprototypes.HTTP_' | |||||
35 |
|
37 | |||
36 | COMMANDS = wireprototypes.commanddict() |
|
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 | def handlehttpv2request(rctx, req, res, checkperm, urlparts): |
|
45 | def handlehttpv2request(rctx, req, res, checkperm, urlparts): | |
39 | from .hgweb import common as hgwebcommon |
|
46 | from .hgweb import common as hgwebcommon | |
40 |
|
47 | |||
@@ -333,12 +340,64 b' def getdispatchrepo(repo, proto, command' | |||||
333 | return repo.filtered('served') |
|
340 | return repo.filtered('served') | |
334 |
|
341 | |||
335 | def dispatch(repo, proto, command): |
|
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 | repo = getdispatchrepo(repo, proto, command) |
|
347 | repo = getdispatchrepo(repo, proto, command) | |
337 |
|
348 | |||
338 |
|
|
349 | entry = COMMANDS[command] | |
|
350 | func = entry.func | |||
|
351 | spec = entry.args | |||
|
352 | ||||
339 | args = proto.getargs(spec) |
|
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 | @interfaceutil.implementer(wireprototypes.baseprotocolhandler) |
|
402 | @interfaceutil.implementer(wireprototypes.baseprotocolhandler) | |
344 | class httpv2protocolhandler(object): |
|
403 | class httpv2protocolhandler(object): | |
@@ -460,7 +519,7 b' def _capabilitiesv2(repo, proto):' | |||||
460 |
|
519 | |||
461 | return proto.addcapabilities(repo, caps) |
|
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 | """Decorator to declare a wire protocol command. |
|
523 | """Decorator to declare a wire protocol command. | |
465 |
|
524 | |||
466 | ``name`` is the name of the wire protocol command being provided. |
|
525 | ``name`` is the name of the wire protocol command being provided. | |
@@ -489,11 +548,21 b' def wireprotocommand(name, args=None, pe' | |||||
489 | because otherwise commands not declaring their permissions could modify |
|
548 | because otherwise commands not declaring their permissions could modify | |
490 | a repository that is supposed to be read-only. |
|
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 | Wire protocol commands are generators of objects to be serialized and |
|
554 | Wire protocol commands are generators of objects to be serialized and | |
493 | sent to the client. |
|
555 | sent to the client. | |
494 |
|
556 | |||
495 | If a command raises an uncaught exception, this will be translated into |
|
557 | If a command raises an uncaught exception, this will be translated into | |
496 | a command error. |
|
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 | transports = {k for k, v in wireprototypes.TRANSPORTS.items() |
|
567 | transports = {k for k, v in wireprototypes.TRANSPORTS.items() | |
499 | if v['version'] == 2} |
|
568 | if v['version'] == 2} | |
@@ -543,12 +612,97 b' def wireprotocommand(name, args=None, pe' | |||||
543 | 'for version 2' % name) |
|
612 | 'for version 2' % name) | |
544 |
|
613 | |||
545 | COMMANDS[name] = wireprototypes.commandentry( |
|
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 | return func |
|
618 | return func | |
549 |
|
619 | |||
550 | return register |
|
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 | @wireprotocommand('branchmap', permission='pull') |
|
706 | @wireprotocommand('branchmap', permission='pull') | |
553 | def branchmapv2(repo, proto): |
|
707 | def branchmapv2(repo, proto): | |
554 | yield {encoding.fromlocal(k): v |
|
708 | yield {encoding.fromlocal(k): v | |
@@ -755,7 +909,11 b' def getfilestore(repo, proto, path):' | |||||
755 | 'example': b'foo.txt', |
|
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 | def filedata(repo, proto, haveparents, nodes, fields, path): |
|
917 | def filedata(repo, proto, haveparents, nodes, fields, path): | |
760 | try: |
|
918 | try: | |
761 | # Extensions may wish to access the protocol handler. |
|
919 | # Extensions may wish to access the protocol handler. | |
@@ -893,7 +1051,8 b' def lookupv2(repo, proto, key):' | |||||
893 | 'example': b'', |
|
1051 | 'example': b'', | |
894 | }, |
|
1052 | }, | |
895 | }, |
|
1053 | }, | |
896 |
permission='pull' |
|
1054 | permission='pull', | |
|
1055 | cachekeyfn=makecommandcachekeyfn('manifestdata', 1, allargs=True)) | |||
897 | def manifestdata(repo, proto, haveparents, nodes, fields, tree): |
|
1056 | def manifestdata(repo, proto, haveparents, nodes, fields, tree): | |
898 | store = repo.manifestlog.getstorage(tree) |
|
1057 | store = repo.manifestlog.getstorage(tree) | |
899 |
|
1058 |
General Comments 0
You need to be logged in to leave comments.
Login now