Mercurial > hg
comparison tests/test-batching.py @ 14621:84094c0d2724
wireproto: add basic command batching infrastructure
Note that localbatch will not be used until we actually have a localpeer to
use it with.
author | Peter Arrenbrecht <peter.arrenbrecht@gmail.com> |
---|---|
date | Tue, 14 Jun 2011 22:51:26 +0200 |
parents | |
children | a7d5816087a9 |
comparison
equal
deleted
inserted
replaced
14620:2b9c32929e62 | 14621:84094c0d2724 |
---|---|
1 # test-batching.py - tests for transparent command batching | |
2 # | |
3 # Copyright 2011 Peter Arrenbrecht <peter@arrenbrecht.ch> | |
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 mercurial.wireproto import localbatch, remotebatch, batchable, future | |
9 | |
10 # equivalent of repo.repository | |
11 class thing(object): | |
12 def hello(self): | |
13 return "Ready." | |
14 | |
15 # equivalent of localrepo.localrepository | |
16 class localthing(thing): | |
17 def foo(self, one, two=None): | |
18 if one: | |
19 return "%s and %s" % (one, two,) | |
20 return "Nope" | |
21 def bar(self, b, a): | |
22 return "%s und %s" % (b, a,) | |
23 def greet(self, name=None): | |
24 return "Hello, %s" % name | |
25 def batch(self): | |
26 '''Support for local batching.''' | |
27 return localbatch(self) | |
28 | |
29 # usage of "thing" interface | |
30 def use(it): | |
31 | |
32 # Direct call to base method shared between client and server. | |
33 print it.hello() | |
34 | |
35 # Direct calls to proxied methods. They cause individual roundtrips. | |
36 print it.foo("Un", two="Deux") | |
37 print it.bar("Eins", "Zwei") | |
38 | |
39 # Batched call to a couple of (possibly proxied) methods. | |
40 batch = it.batch() | |
41 # The calls return futures to eventually hold results. | |
42 foo = batch.foo(one="One", two="Two") | |
43 foo2 = batch.foo(None) | |
44 bar = batch.bar("Eins", "Zwei") | |
45 # We can call non-batchable proxy methods, but the break the current batch | |
46 # request and cause additional roundtrips. | |
47 greet = batch.greet(name="John Smith") | |
48 # We can also add local methods into the mix, but they break the batch too. | |
49 hello = batch.hello() | |
50 bar2 = batch.bar(b="Uno", a="Due") | |
51 # Only now are all the calls executed in sequence, with as few roundtrips | |
52 # as possible. | |
53 batch.submit() | |
54 # After the call to submit, the futures actually contain values. | |
55 print foo.value | |
56 print foo2.value | |
57 print bar.value | |
58 print greet.value | |
59 print hello.value | |
60 print bar2.value | |
61 | |
62 # local usage | |
63 mylocal = localthing() | |
64 print | |
65 print "== Local" | |
66 use(mylocal) | |
67 | |
68 # demo remoting; mimicks what wireproto and HTTP/SSH do | |
69 | |
70 # shared | |
71 | |
72 def escapearg(plain): | |
73 return (plain | |
74 .replace(':', '::') | |
75 .replace(',', ':,') | |
76 .replace(';', ':;') | |
77 .replace('=', ':=')) | |
78 def unescapearg(escaped): | |
79 return (escaped | |
80 .replace(':=', '=') | |
81 .replace(':;', ';') | |
82 .replace(':,', ',') | |
83 .replace('::', ':')) | |
84 | |
85 # server side | |
86 | |
87 # equivalent of wireproto's global functions | |
88 class server: | |
89 def __init__(self, local): | |
90 self.local = local | |
91 def _call(self, name, args): | |
92 args = dict(arg.split('=', 1) for arg in args) | |
93 return getattr(self, name)(**args) | |
94 def perform(self, req): | |
95 print "REQ:", req | |
96 name, args = req.split('?', 1) | |
97 args = args.split('&') | |
98 vals = dict(arg.split('=', 1) for arg in args) | |
99 res = getattr(self, name)(**vals) | |
100 print " ->", res | |
101 return res | |
102 def batch(self, cmds): | |
103 res = [] | |
104 for pair in cmds.split(';'): | |
105 name, args = pair.split(':', 1) | |
106 vals = {} | |
107 for a in args.split(','): | |
108 if a: | |
109 n, v = a.split('=') | |
110 vals[n] = unescapearg(v) | |
111 res.append(escapearg(getattr(self, name)(**vals))) | |
112 return ';'.join(res) | |
113 def foo(self, one, two): | |
114 return mangle(self.local.foo(unmangle(one), unmangle(two))) | |
115 def bar(self, b, a): | |
116 return mangle(self.local.bar(unmangle(b), unmangle(a))) | |
117 def greet(self, name): | |
118 return mangle(self.local.greet(unmangle(name))) | |
119 myserver = server(mylocal) | |
120 | |
121 # local side | |
122 | |
123 # equivalent of wireproto.encode/decodelist, that is, type-specific marshalling | |
124 # here we just transform the strings a bit to check we're properly en-/decoding | |
125 def mangle(s): | |
126 return ''.join(chr(ord(c) + 1) for c in s) | |
127 def unmangle(s): | |
128 return ''.join(chr(ord(c) - 1) for c in s) | |
129 | |
130 # equivalent of wireproto.wirerepository and something like http's wire format | |
131 class remotething(thing): | |
132 def __init__(self, server): | |
133 self.server = server | |
134 def _submitone(self, name, args): | |
135 req = name + '?' + '&'.join(['%s=%s' % (n, v) for n, v in args]) | |
136 return self.server.perform(req) | |
137 def _submitbatch(self, cmds): | |
138 req = [] | |
139 for name, args in cmds: | |
140 args = ','.join(n + '=' + escapearg(v) for n, v in args) | |
141 req.append(name + ':' + args) | |
142 req = ';'.join(req) | |
143 res = self._submitone('batch', [('cmds', req,)]) | |
144 return res.split(';') | |
145 | |
146 def batch(self): | |
147 return remotebatch(self) | |
148 | |
149 @batchable | |
150 def foo(self, one, two=None): | |
151 if not one: | |
152 yield "Nope", None | |
153 encargs = [('one', mangle(one),), ('two', mangle(two),)] | |
154 encresref = future() | |
155 yield encargs, encresref | |
156 yield unmangle(encresref.value) | |
157 | |
158 @batchable | |
159 def bar(self, b, a): | |
160 encresref = future() | |
161 yield [('b', mangle(b),), ('a', mangle(a),)], encresref | |
162 yield unmangle(encresref.value) | |
163 | |
164 # greet is coded directly. It therefore does not support batching. If it | |
165 # does appear in a batch, the batch is split around greet, and the call to | |
166 # greet is done in its own roundtrip. | |
167 def greet(self, name=None): | |
168 return unmangle(self._submitone('greet', [('name', mangle(name),)])) | |
169 | |
170 # demo remote usage | |
171 | |
172 myproxy = remotething(myserver) | |
173 print | |
174 print "== Remote" | |
175 use(myproxy) |