[RFC,v2,1/4] sstate: add a LockedSet class as python set() is not thread safe

Message ID 20220416222846.219151-1-quaresma.jose@gmail.com
State New
Headers show
Series [RFC,v2,1/4] sstate: add a LockedSet class as python set() is not thread safe | expand

Commit Message

Jose Quaresma April 16, 2022, 10:28 p.m. UTC
With this LockedSet python class we can call the 'add' and 'remove'
safely inside the ThreadedPool.

This piece of code is taen from the stackoverflow
https://stackoverflow.com/questions/13610654/how-to-make-built-in-containers-sets-dicts-lists-thread-safe

Fixes [YOCTO #14775] -- https://bugzilla.yoctoproject.org/show_bug.cgi?id=14775

Signed-off-by: Jose Quaresma <quaresma.jose@gmail.com>
---
 meta/classes/sstate.bbclass | 24 ++++++++++++++++++++++--
 1 file changed, 22 insertions(+), 2 deletions(-)

Comments

Richard Purdie April 17, 2022, 1:39 p.m. UTC | #1
On Sat, 2022-04-16 at 23:28 +0100, Jose Quaresma wrote:
> With this LockedSet python class we can call the 'add' and 'remove'
> safely inside the ThreadedPool.
> 
> This piece of code is taen from the stackoverflow
> https://stackoverflow.com/questions/13610654/how-to-make-built-in-containers-sets-dicts-lists-thread-safe
> 
> Fixes [YOCTO #14775] -- https://bugzilla.yoctoproject.org/show_bug.cgi?id=14775
> 
> Signed-off-by: Jose Quaresma <quaresma.jose@gmail.com>
> ---
>  meta/classes/sstate.bbclass | 24 ++++++++++++++++++++++--
>  1 file changed, 22 insertions(+), 2 deletions(-)
> 
> diff --git a/meta/classes/sstate.bbclass b/meta/classes/sstate.bbclass
> index 1c0cae4893..a3ba748a1e 100644
> --- a/meta/classes/sstate.bbclass
> +++ b/meta/classes/sstate.bbclass
> @@ -918,8 +918,28 @@ sstate_unpack_package () {
>  BB_HASHCHECK_FUNCTION = "sstate_checkhashes"
>  
>  def sstate_checkhashes(sq_data, d, siginfo=False, currentcount=0, summary=True, **kwargs):
> -    found = set()
> -    missed = set()
> +    # https://stackoverflow.com/questions/13610654/how-to-make-built-in-containers-sets-dicts-lists-thread-safe
> +    import threading
> +    class LockedSet(set):
> +        """A set where add(), remove(), and 'in' operator are thread-safe"""
> +        def __init__(self, *args, **kwargs):
> +            self._lock = threading.Lock()
> +            super(LockedSet, self).__init__(*args, **kwargs)
> +
> +        def add(self, elem):
> +            with self._lock:
> +                super(LockedSet, self).add(elem)
> +
> +        def remove(self, elem):
> +            with self._lock:
> +                super(LockedSet, self).remove(elem)
> +
> +        def __contains__(self, elem):
> +            with self._lock:
> +                super(LockedSet, self).__contains__(elem)
> +
> +    found = LockedSet()
> +    missed = LockedSet()
>  
>      def gethash(task):
>          return sq_data['unihash'][task]


The series blew up on the autobuilder:

testsdkest:
https://autobuilder.yoctoproject.org/typhoon/#/builders/53/builds/5079/steps/17/logs/stdio
oe-selftest:
https://autobuilder.yoctoproject.org/typhoon/#/builders/79/builds/3461/steps/15/logs/stdio
and many more, see:
https://autobuilder.yoctoproject.org/typhoon/#/builders/83/builds/3535

Cheers,

Richard
Richard Purdie April 17, 2022, 1:41 p.m. UTC | #2
On Sun, 2022-04-17 at 14:39 +0100, Richard Purdie via lists.openembedded.org
wrote:
> On Sat, 2022-04-16 at 23:28 +0100, Jose Quaresma wrote:
> > With this LockedSet python class we can call the 'add' and 'remove'
> > safely inside the ThreadedPool.
> > 
> > This piece of code is taen from the stackoverflow
> > https://stackoverflow.com/questions/13610654/how-to-make-built-in-containers-sets-dicts-lists-thread-safe
> > 
> > Fixes [YOCTO #14775] -- https://bugzilla.yoctoproject.org/show_bug.cgi?id=14775
> > 
> > Signed-off-by: Jose Quaresma <quaresma.jose@gmail.com>
> > ---
> >  meta/classes/sstate.bbclass | 24 ++++++++++++++++++++++--
> >  1 file changed, 22 insertions(+), 2 deletions(-)
> > 
> > diff --git a/meta/classes/sstate.bbclass b/meta/classes/sstate.bbclass
> > index 1c0cae4893..a3ba748a1e 100644
> > --- a/meta/classes/sstate.bbclass
> > +++ b/meta/classes/sstate.bbclass
> > @@ -918,8 +918,28 @@ sstate_unpack_package () {
> >  BB_HASHCHECK_FUNCTION = "sstate_checkhashes"
> >  
> >  def sstate_checkhashes(sq_data, d, siginfo=False, currentcount=0, summary=True, **kwargs):
> > -    found = set()
> > -    missed = set()
> > +    # https://stackoverflow.com/questions/13610654/how-to-make-built-in-containers-sets-dicts-lists-thread-safe
> > +    import threading
> > +    class LockedSet(set):
> > +        """A set where add(), remove(), and 'in' operator are thread-safe"""
> > +        def __init__(self, *args, **kwargs):
> > +            self._lock = threading.Lock()
> > +            super(LockedSet, self).__init__(*args, **kwargs)
> > +
> > +        def add(self, elem):
> > +            with self._lock:
> > +                super(LockedSet, self).add(elem)
> > +
> > +        def remove(self, elem):
> > +            with self._lock:
> > +                super(LockedSet, self).remove(elem)
> > +
> > +        def __contains__(self, elem):
> > +            with self._lock:
> > +                super(LockedSet, self).__contains__(elem)
> > +
> > +    found = LockedSet()
> > +    missed = LockedSet()
> >  
> >      def gethash(task):
> >          return sq_data['unihash'][task]
> 
> 
> The series blew up on the autobuilder:
> 
> testsdkest:
> https://autobuilder.yoctoproject.org/typhoon/#/builders/53/builds/5079/steps/17/logs/stdio
> oe-selftest:
> https://autobuilder.yoctoproject.org/typhoon/#/builders/79/builds/3461/steps/15/logs/stdio
> and many more, see:
> https://autobuilder.yoctoproject.org/typhoon/#/builders/83/builds/3535

That last one should be:
https://autobuilder.yoctoproject.org/typhoon/#/builders/83/builds/3539

Cheers,

Richard
Richard Purdie April 17, 2022, 8:09 p.m. UTC | #3
On Sun, 2022-04-17 at 14:41 +0100, Richard Purdie via lists.openembedded.org
wrote:
> On Sun, 2022-04-17 at 14:39 +0100, Richard Purdie via lists.openembedded.org
> wrote:
> > On Sat, 2022-04-16 at 23:28 +0100, Jose Quaresma wrote:
> > > With this LockedSet python class we can call the 'add' and 'remove'
> > > safely inside the ThreadedPool.
> > > 
> > > This piece of code is taen from the stackoverflow
> > > https://stackoverflow.com/questions/13610654/how-to-make-built-in-containers-sets-dicts-lists-thread-safe
> > > 
> > > Fixes [YOCTO #14775] -- https://bugzilla.yoctoproject.org/show_bug.cgi?id=14775
> > > 
> > > Signed-off-by: Jose Quaresma <quaresma.jose@gmail.com>
> > > ---
> > >  meta/classes/sstate.bbclass | 24 ++++++++++++++++++++++--
> > >  1 file changed, 22 insertions(+), 2 deletions(-)
> > > 
> > > diff --git a/meta/classes/sstate.bbclass b/meta/classes/sstate.bbclass
> > > index 1c0cae4893..a3ba748a1e 100644
> > > --- a/meta/classes/sstate.bbclass
> > > +++ b/meta/classes/sstate.bbclass
> > > @@ -918,8 +918,28 @@ sstate_unpack_package () {
> > >  BB_HASHCHECK_FUNCTION = "sstate_checkhashes"
> > >  
> > >  def sstate_checkhashes(sq_data, d, siginfo=False, currentcount=0, summary=True, **kwargs):
> > > -    found = set()
> > > -    missed = set()
> > > +    # https://stackoverflow.com/questions/13610654/how-to-make-built-in-containers-sets-dicts-lists-thread-safe
> > > +    import threading
> > > +    class LockedSet(set):
> > > +        """A set where add(), remove(), and 'in' operator are thread-safe"""
> > > +        def __init__(self, *args, **kwargs):
> > > +            self._lock = threading.Lock()
> > > +            super(LockedSet, self).__init__(*args, **kwargs)
> > > +
> > > +        def add(self, elem):
> > > +            with self._lock:
> > > +                super(LockedSet, self).add(elem)
> > > +
> > > +        def remove(self, elem):
> > > +            with self._lock:
> > > +                super(LockedSet, self).remove(elem)
> > > +
> > > +        def __contains__(self, elem):
> > > +            with self._lock:
> > > +                super(LockedSet, self).__contains__(elem)
> > > +
> > > +    found = LockedSet()
> > > +    missed = LockedSet()
> > >  
> > >      def gethash(task):
> > >          return sq_data['unihash'][task]
> > 
> > 
> > The series blew up on the autobuilder:
> > 
> > testsdkest:
> > https://autobuilder.yoctoproject.org/typhoon/#/builders/53/builds/5079/steps/17/logs/stdio
> > oe-selftest:
> > https://autobuilder.yoctoproject.org/typhoon/#/builders/79/builds/3461/steps/15/logs/stdio
> > and many more, see:
> > https://autobuilder.yoctoproject.org/typhoon/#/builders/83/builds/3535
> 
> That last one should be:
> https://autobuilder.yoctoproject.org/typhoon/#/builders/83/builds/3539

diff --git a/meta/classes/sstate.bbclass b/meta/classes/sstate.bbclass
index 7a18d9d554..99d125b8c5 100644
--- a/meta/classes/sstate.bbclass
+++ b/meta/classes/sstate.bbclass
@@ -1058,7 +1058,7 @@ def sstate_checkhashes(sq_data, d, siginfo=False, currentcount=0, summary=True,
                 connection_cache_pool = Queue(nproc)
                 checkstatus_init()
                 with concurrent.futures.ThreadPoolExecutor(max_workers=nproc) as executor:
-                    executor.map(checkstatus, tasks)
+                    executor.map(checkstatus, tasks.copy())
                 checkstatus_end()
                 bb.event.disable_threadlock()

fixes things.

Cheers,

Richard
Jose Quaresma April 18, 2022, 5:31 a.m. UTC | #4
Richard Purdie <richard.purdie@linuxfoundation.org> escreveu no dia
domingo, 17/04/2022 à(s) 21:09:

> On Sun, 2022-04-17 at 14:41 +0100, Richard Purdie via
> lists.openembedded.org
> wrote:
> > On Sun, 2022-04-17 at 14:39 +0100, Richard Purdie via
> lists.openembedded.org
> > wrote:
> > > On Sat, 2022-04-16 at 23:28 +0100, Jose Quaresma wrote:
> > > > With this LockedSet python class we can call the 'add' and 'remove'
> > > > safely inside the ThreadedPool.
> > > >
> > > > This piece of code is taen from the stackoverflow
> > > >
> https://stackoverflow.com/questions/13610654/how-to-make-built-in-containers-sets-dicts-lists-thread-safe
> > > >
> > > > Fixes [YOCTO #14775] --
> https://bugzilla.yoctoproject.org/show_bug.cgi?id=14775
> > > >
> > > > Signed-off-by: Jose Quaresma <quaresma.jose@gmail.com>
> > > > ---
> > > >  meta/classes/sstate.bbclass | 24 ++++++++++++++++++++++--
> > > >  1 file changed, 22 insertions(+), 2 deletions(-)
> > > >
> > > > diff --git a/meta/classes/sstate.bbclass
> b/meta/classes/sstate.bbclass
> > > > index 1c0cae4893..a3ba748a1e 100644
> > > > --- a/meta/classes/sstate.bbclass
> > > > +++ b/meta/classes/sstate.bbclass
> > > > @@ -918,8 +918,28 @@ sstate_unpack_package () {
> > > >  BB_HASHCHECK_FUNCTION = "sstate_checkhashes"
> > > >
> > > >  def sstate_checkhashes(sq_data, d, siginfo=False, currentcount=0,
> summary=True, **kwargs):
> > > > -    found = set()
> > > > -    missed = set()
> > > > +    #
> https://stackoverflow.com/questions/13610654/how-to-make-built-in-containers-sets-dicts-lists-thread-safe
> > > > +    import threading
> > > > +    class LockedSet(set):
> > > > +        """A set where add(), remove(), and 'in' operator are
> thread-safe"""
> > > > +        def __init__(self, *args, **kwargs):
> > > > +            self._lock = threading.Lock()
> > > > +            super(LockedSet, self).__init__(*args, **kwargs)
> > > > +
> > > > +        def add(self, elem):
> > > > +            with self._lock:
> > > > +                super(LockedSet, self).add(elem)
> > > > +
> > > > +        def remove(self, elem):
> > > > +            with self._lock:
> > > > +                super(LockedSet, self).remove(elem)
> > > > +
> > > > +        def __contains__(self, elem):
> > > > +            with self._lock:
> > > > +                super(LockedSet, self).__contains__(elem)
> > > > +
> > > > +    found = LockedSet()
> > > > +    missed = LockedSet()
> > > >
> > > >      def gethash(task):
> > > >          return sq_data['unihash'][task]
> > >
> > >
> > > The series blew up on the autobuilder:
> > >
> > > testsdkest:
> > >
> https://autobuilder.yoctoproject.org/typhoon/#/builders/53/builds/5079/steps/17/logs/stdio
> > > oe-selftest:
> > >
> https://autobuilder.yoctoproject.org/typhoon/#/builders/79/builds/3461/steps/15/logs/stdio
> > > and many more, see:
> > > https://autobuilder.yoctoproject.org/typhoon/#/builders/83/builds/3535
> >
> > That last one should be:
> > https://autobuilder.yoctoproject.org/typhoon/#/builders/83/builds/3539
>
> diff --git a/meta/classes/sstate.bbclass b/meta/classes/sstate.bbclass
> index 7a18d9d554..99d125b8c5 100644
> --- a/meta/classes/sstate.bbclass
> +++ b/meta/classes/sstate.bbclass
> @@ -1058,7 +1058,7 @@ def sstate_checkhashes(sq_data, d, siginfo=False,
> currentcount=0, summary=True,
>                  connection_cache_pool = Queue(nproc)
>                  checkstatus_init()
>                  with
> concurrent.futures.ThreadPoolExecutor(max_workers=nproc) as executor:
> -                    executor.map(checkstatus, tasks)
> +                    executor.map(checkstatus, tasks.copy())
>                  checkstatus_end()
>                  bb.event.disable_threadlock()
>
> fixes things.
>
> Cheers,
>
> Richard
>
>
Thanks for testing and fixing, this can be because the tasks collection has
been changed inside the thread pool.
I'll wait a few more days to see if there are any more comments and I hope
to be able to finish during this week.

Patch

diff --git a/meta/classes/sstate.bbclass b/meta/classes/sstate.bbclass
index 1c0cae4893..a3ba748a1e 100644
--- a/meta/classes/sstate.bbclass
+++ b/meta/classes/sstate.bbclass
@@ -918,8 +918,28 @@  sstate_unpack_package () {
 BB_HASHCHECK_FUNCTION = "sstate_checkhashes"
 
 def sstate_checkhashes(sq_data, d, siginfo=False, currentcount=0, summary=True, **kwargs):
-    found = set()
-    missed = set()
+    # https://stackoverflow.com/questions/13610654/how-to-make-built-in-containers-sets-dicts-lists-thread-safe
+    import threading
+    class LockedSet(set):
+        """A set where add(), remove(), and 'in' operator are thread-safe"""
+        def __init__(self, *args, **kwargs):
+            self._lock = threading.Lock()
+            super(LockedSet, self).__init__(*args, **kwargs)
+
+        def add(self, elem):
+            with self._lock:
+                super(LockedSet, self).add(elem)
+
+        def remove(self, elem):
+            with self._lock:
+                super(LockedSet, self).remove(elem)
+
+        def __contains__(self, elem):
+            with self._lock:
+                super(LockedSet, self).__contains__(elem)
+
+    found = LockedSet()
+    missed = LockedSet()
 
     def gethash(task):
         return sq_data['unihash'][task]