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)