Re: sharing a dict between child processes

Sean DiZazzo <[email protected]> Wed, 6 Nov 2019 21:29:34 -0800
Newsgroups gmane.comp.python.twisted
Message-ID <CANzH6evFWU5i_97t-uHUcziwbh9WKvt06Sd30496OPjbUpfP4g@mail.gmail.com>
--===============3567177367930515327==
Content-Type: multipart/alternative; boundary="000000000000034e110596baf405"

--000000000000034e110596baf405
Content-Type: text/plain; charset="UTF-8"

If you need guaranteed delivery of the data, why not just use a TCP
connection to the unix socket, instead of a UDP connection which inherently
can lose data?  In that case I don't think your patch would be needed.

I didn't look at the source, so perhaps I missed something.

On Wed, Nov 6, 2019 at 9:10 AM Scott, Barry <[email protected]>
wrote:

> On Wednesday, 6 November 2019 16:43:52 GMT Waqar Khan wrote:
> > Hi Barry,
> >         Thanks for the response. Where can I read more about (1). It
> seems
> > like that is something I need to explore.
> > As we already have (2) (cache for each process).
> > Thanks again for your help.
>
> We use the UDS (Unix domain sockets) to talk to a master process.
> Twisted has support for this. But you need a small patch to avoid data
> lose.
>
> UDS does not lose data and is message based, not bytes based. We
> use pickle to encode requests and responses.
>
> Barry
>
> The patch is:
>
> --- Twisted-18.4.0.orig/src/twisted/internet/unix.py.orig       2018-08-01
> 12:45:38.711115425 +0100
> +++ Twisted-18.4.0/src/twisted/internet/unix.py 2018-08-01
> 12:45:47.946115123
> +0100
> @@ -509,11 +509,6 @@
>                  return self.write(datagram, address)
>              elif no == EMSGSIZE:
>                  raise error.MessageLengthError("message too long")
> -            elif no == EAGAIN:
> -                # oh, well, drop the data. The only difference from UDP
> -                # is that UDP won't ever notice.
> -                # TODO: add TCP-like buffering
> -                pass
>              else:
>                  raise
>
> You then have to handle the EAGAIN error and do retries yourself.
> As it stands the patch is not good enough to put into twisted as a
> full fix would need to put the handling of the retries into twisted.
>
> I guess (2) does not work for you as the cache hit rate is low
> and you need to share the cache to get a benefit. Cache entries
> only get used a few times?
>
> In our case the hit rate is high (99%+) and we just pay the cost of
> populating the caches on process start up, which ends up being
> noise.
>
> Barry
>
> >
> > On Wed, Nov 6, 2019 at 8:39 AM Scott, Barry <[email protected]>
> >
> > wrote:
> > > On Wednesday, 6 November 2019 14:21:22 GMT Maarten ter Huurne wrote:
> > > > On Wednesday, 6 November 2019 07:19:56 CET Waqar Khan wrote:
> > > > > Hi,
> > > > > So, I am writing a twisted server. This server spawn multiple child
> > > > > processes using reactor spawnProcess that initializes a process
> > > > > protocol.
> > > > >
> > > > > Now, each of the childprocess receives some REST requests. Each
> > > > > process has a dict that acts as cache.
> > > > > Now, I want to share dict across processes.
> > > > > In general, python has SharedMemoryManager in multiprocessing
> module
> > > > > which would have helped.
> > > > >
> https://docs.python.org/3/library/multiprocessing.shared_memory.html#m
> > > > > ultiprocessing.managers.SharedMemoryManager.SharedMemory But since
> I
> > > > > am using twisted internal process implementation, how do I share
> this
> > > > > dict across the processes so that all the processes use this common
> > > > > cache?
> > > >
> > > > Keeping a dictionary in SharedMemoryManager seems far from trivial. I
> > > > don't think you can allocate arbitrary Python objects in the shared
> > > > memory and even if you could, you would run into problems when one
> > > > process mutates the dictionary while another is looking up something
> or
> > > > also mutating it.
> > > >
> > > > It could in theory work if you implement a custom lock-less
> dictionary,
> > > > but that would be a lot of work and hard to get right. Also having
> > > > shared memory mutations be synced between multiple CPU cores could
> > > > degrade performance, since keeping core-local CPU caches in sync is
> > > > expensive.
> > > >
> > > > Would it be an option to have only one process accept the REST
> requests,
> > > > check whether the result is in the cache and only distribute work to
> the
> > > > other processes if you get a cache miss? Typically the case where an
> > > > answer is cached is pretty fast, so perhaps you don't need multiple
> > > > processes to handle incoming requests.
> > >
> > > We have used a couple of ways to cache.
> > > 1. Use a singleton process to hold the cache and ask it, via IPC, for
> > > answers
> > > from the other process.
> > > 2. have a cache in each process
> > >
> > > Barry
> > >
> > > > Bye,
> > > >
> > > >               Maarten
> > > >
> > > > _______________________________________________
> > > > Twisted-Python mailing list
> > > > [email protected]
> > > > https://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
> > >
> > > _______________________________________________
> > > Twisted-Python mailing list
> > > [email protected]
> > > https://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
>
>
>
>
> _______________________________________________
> Twisted-Python mailing list
> [email protected]
> https://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
>

--000000000000034e110596baf405
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

<div dir=3D"ltr"><div dir=3D"ltr">If you need guaranteed=C2=A0delivery of t=
he data, why not just use a TCP connection to the unix socket, instead of a=
 UDP connection which inherently can lose data?=C2=A0 In that case I don&#3=
9;t think your patch would be needed.<input name=3D"virtru-metadata" type=
=3D"hidden" value=3D"{&quot;email-policy&quot;:{&quot;state&quot;:&quot;clo=
sed&quot;,&quot;expirationUnit&quot;:&quot;days&quot;,&quot;disableCopyPast=
e&quot;:false,&quot;disablePrint&quot;:false,&quot;disableForwarding&quot;:=
false,&quot;enableNoauth&quot;:false,&quot;persistentProtection&quot;:false=
,&quot;expandedWatermarking&quot;:false,&quot;expires&quot;:false,&quot;isM=
anaged&quot;:false},&quot;attachments&quot;:{},&quot;compose-id&quot;:&quot=
;1&quot;,&quot;compose-window&quot;:{&quot;secure&quot;:false}}"><div><br><=
/div><div>I didn&#39;t look at the source, so perhaps I missed something.</=
div></div><br><div class=3D"gmail_quote" style=3D""><div dir=3D"ltr" class=
=3D"gmail_attr">On Wed, Nov 6, 2019 at 9:10 AM Scott, Barry &lt;<a href=3D"=
mailto:[email protected]">[email protected]</a>&gt; wrote=
:<br></div><blockquote class=3D"gmail_quote" style=3D"margin:0px 0px 0px 0.=
8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">On Wednesday, =
6 November 2019 16:43:52 GMT Waqar Khan wrote:<br>
&gt; Hi Barry,<br>
&gt;=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Thanks for the response. Where can I =
read more about (1). It seems<br>
&gt; like that is something I need to explore.<br>
&gt; As we already have (2) (cache for each process).<br>
&gt; Thanks again for your help.<br>
<br>
We use the UDS (Unix domain sockets) to talk to a master process.<br>
Twisted has support for this. But you need a small patch to avoid data lose=
.<br>
<br>
UDS does not lose data and is message based, not bytes based. We<br>
use pickle to encode requests and responses.<br>
<br>
Barry<br>
<br>
The patch is:<br>
<br>
--- Twisted-18.4.0.orig/src/twisted/internet/unix.py.orig=C2=A0 =C2=A0 =C2=
=A0 =C2=A02018-08-01 <br>
12:45:38.711115425 +0100<br>
+++ Twisted-18.4.0/src/twisted/internet/unix.py 2018-08-01 12:45:47.9461151=
23 <br>
+0100<br>
@@ -509,11 +509,6 @@<br>
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return self.w=
rite(datagram, address)<br>
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0elif no =3D=3D EMSGSIZE:<br=
>
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0raise error.M=
essageLengthError(&quot;message too long&quot;)<br>
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 elif no =3D=3D EAGAIN:<br>
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 # oh, well, drop t=
he data. The only difference from UDP<br>
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 # is that UDP won&=
#39;t ever notice.<br>
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 # TODO: add TCP-li=
ke buffering<br>
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 pass<br>
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0else:<br>
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0raise<br>
<br>
You then have to handle the EAGAIN error and do retries yourself.<br>
As it stands the patch is not good enough to put into twisted as a<br>
full fix would need to put the handling of the retries into twisted.<br>
<br>
I guess (2) does not work for you as the cache hit rate is low<br>
and you need to share the cache to get a benefit. Cache entries<br>
only get used a few times?<br>
<br>
In our case the hit rate is high (99%+) and we just pay the cost of<br>
populating the caches on process start up, which ends up being<br>
noise.<br>
<br>
Barry<br>
<br>
&gt; <br>
&gt; On Wed, Nov 6, 2019 at 8:39 AM Scott, Barry &lt;<a href=3D"mailto:barr=
[email protected]" target=3D"_blank">[email protected]</a>&gt=
;<br>
&gt; <br>
&gt; wrote:<br>
&gt; &gt; On Wednesday, 6 November 2019 14:21:22 GMT Maarten ter Huurne wro=
te:<br>
&gt; &gt; &gt; On Wednesday, 6 November 2019 07:19:56 CET Waqar Khan wrote:=
<br>
&gt; &gt; &gt; &gt; Hi,<br>
&gt; &gt; &gt; &gt; So, I am writing a twisted server. This server spawn mu=
ltiple child<br>
&gt; &gt; &gt; &gt; processes using reactor spawnProcess that initializes a=
 process<br>
&gt; &gt; &gt; &gt; protocol.<br>
&gt; &gt; &gt; &gt; <br>
&gt; &gt; &gt; &gt; Now, each of the childprocess receives some REST reques=
ts. Each<br>
&gt; &gt; &gt; &gt; process has a dict that acts as cache.<br>
&gt; &gt; &gt; &gt; Now, I want to share dict across processes.<br>
&gt; &gt; &gt; &gt; In general, python has SharedMemoryManager in multiproc=
essing module<br>
&gt; &gt; &gt; &gt; which would have helped.<br>
&gt; &gt; &gt; &gt; <a href=3D"https://docs.python.org/3/library/multiproce=
ssing.shared_memory.html#m" rel=3D"noreferrer" target=3D"_blank">https://do=
cs.python.org/3/library/multiprocessing.shared_memory.html#m</a><br>
&gt; &gt; &gt; &gt; ultiprocessing.managers.SharedMemoryManager.SharedMemor=
y But since I<br>
&gt; &gt; &gt; &gt; am using twisted internal process implementation, how d=
o I share this<br>
&gt; &gt; &gt; &gt; dict across the processes so that all the processes use=
 this common<br>
&gt; &gt; &gt; &gt; cache?<br>
&gt; &gt; &gt; <br>
&gt; &gt; &gt; Keeping a dictionary in SharedMemoryManager seems far from t=
rivial. I<br>
&gt; &gt; &gt; don&#39;t think you can allocate arbitrary Python objects in=
 the shared<br>
&gt; &gt; &gt; memory and even if you could, you would run into problems wh=
en one<br>
&gt; &gt; &gt; process mutates the dictionary while another is looking up s=
omething or<br>
&gt; &gt; &gt; also mutating it.<br>
&gt; &gt; &gt; <br>
&gt; &gt; &gt; It could in theory work if you implement a custom lock-less =
dictionary,<br>
&gt; &gt; &gt; but that would be a lot of work and hard to get right. Also =
having<br>
&gt; &gt; &gt; shared memory mutations be synced between multiple CPU cores=
 could<br>
&gt; &gt; &gt; degrade performance, since keeping core-local CPU caches in =
sync is<br>
&gt; &gt; &gt; expensive.<br>
&gt; &gt; &gt; <br>
&gt; &gt; &gt; Would it be an option to have only one process accept the RE=
ST requests,<br>
&gt; &gt; &gt; check whether the result is in the cache and only distribute=
 work to the<br>
&gt; &gt; &gt; other processes if you get a cache miss? Typically the case =
where an<br>
&gt; &gt; &gt; answer is cached is pretty fast, so perhaps you don&#39;t ne=
ed multiple<br>
&gt; &gt; &gt; processes to handle incoming requests.<br>
&gt; &gt; <br>
&gt; &gt; We have used a couple of ways to cache.<br>
&gt; &gt; 1. Use a singleton process to hold the cache and ask it, via IPC,=
 for<br>
&gt; &gt; answers<br>
&gt; &gt; from the other process.<br>
&gt; &gt; 2. have a cache in each process<br>
&gt; &gt; <br>
&gt; &gt; Barry<br>
&gt; &gt; <br>
&gt; &gt; &gt; Bye,<br>
&gt; &gt; &gt; <br>
&gt; &gt; &gt;=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Maarte=
n<br>
&gt; &gt; &gt; <br>
&gt; &gt; &gt; _______________________________________________<br>
&gt; &gt; &gt; Twisted-Python mailing list<br>
&gt; &gt; &gt; <a href=3D"mailto:[email protected]" target=
=3D"_blank">[email protected]</a><br>
&gt; &gt; &gt; <a href=3D"https://twistedmatrix.com/cgi-bin/mailman/listinf=
o/twisted-python" rel=3D"noreferrer" target=3D"_blank">https://twistedmatri=
x.com/cgi-bin/mailman/listinfo/twisted-python</a><br>
&gt; &gt; <br>
&gt; &gt; _______________________________________________<br>
&gt; &gt; Twisted-Python mailing list<br>
&gt; &gt; <a href=3D"mailto:[email protected]" target=3D"_bl=
ank">[email protected]</a><br>
&gt; &gt; <a href=3D"https://twistedmatrix.com/cgi-bin/mailman/listinfo/twi=
sted-python" rel=3D"noreferrer" target=3D"_blank">https://twistedmatrix.com=
/cgi-bin/mailman/listinfo/twisted-python</a><br>
<br>
<br>
<br>
<br>
_______________________________________________<br>
Twisted-Python mailing list<br>
<a href=3D"mailto:[email protected]" target=3D"_blank">Twist=
[email protected]</a><br>
<a href=3D"https://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-pytho=
n" rel=3D"noreferrer" target=3D"_blank">https://twistedmatrix.com/cgi-bin/m=
ailman/listinfo/twisted-python</a><br>
</blockquote></div></div>

--000000000000034e110596baf405--

--===============3567177367930515327==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: inline

X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18KVHdpc3RlZC1Q
eXRob24gbWFpbGluZyBsaXN0ClR3aXN0ZWQtUHl0aG9uQHR3aXN0ZWRtYXRyaXguY29tCmh0dHBz
Oi8vdHdpc3RlZG1hdHJpeC5jb20vY2dpLWJpbi9tYWlsbWFuL2xpc3RpbmZvL3R3aXN0ZWQtcHl0
aG9uCg==

--===============3567177367930515327==--