Gentoo Archives: gentoo-portage-dev

From: Brian Harring <ferringb@g.o>
To: gentoo-portage-dev@l.g.o
Subject: Re: [gentoo-portage-dev] Plugin backport PATCH (1/2)/(2/2)
Date: Sat, 19 Nov 2005 11:50:33
Message-Id: 20051119114855.GA9255@nightcrawler
In Reply to: Re: [gentoo-portage-dev] Plugin backport PATCH (1/2)/(2/2) by Jason Stubbs
1 On Sat, Nov 19, 2005 at 01:12:46PM +0900, Jason Stubbs wrote:
2 > > > +++ ./pym/portage_locks.py      2005-11-16 01:56:25.152161768 -0500
3 > > > @@ -358,3 +359,91 @@
4 > > > +# should the fd be left open indefinitely?
5 > > > +# IMO, it shouldn't, but opening/closing everytime around is expensive
6 > > >
7 > > > It should be closed when locks are released, I think. Otherwise the file may
8 > > > be deleted and recreated while one FsLock instance has it open which could
9 > > > create situations where two FsLocks are trying to lock the same path but
10 > > > using different fds.
11 > > Leave it open, on locking do a inode verification of the existing
12 > > handle- if it's behind, close the fd, open, resume locking (offhand).
13 > >
14 > > Race potential, but that can be addressed by getting the read lock,
15 > > then doing a second (or first, if it's not re-upping a lock) inode
16 > > verification
17 >
18 > Umm.. getting complicated. How often are locks actually obtained, released
19 > and then obtained a second time? Sacrificing readability for performance in
20 > corner cases doesn't seem quite right.
21 It's not really that complicated, and (imo) is a requisite if the
22 locking obj is used for synchronizing during deletions;
23
24 lock_obtained = False
25 while not lock_obtained:
26 if self.fd:
27 self.read_lock()
28 # check that the file didn't change under us
29 if os.fstat(self.fd).st_ino != os.stat(self.path).st_ino):
30 self.release_lock()
31 else:
32 lock_obtained = True
33
34 if not self.fd:
35 self._acquire_fd()
36
37 Would be a rough example of it, bordering pseudocode (pardon bugs,
38 5:48am my time right now). :)
39
40 EIther way, yes, it's a bit complicated, but locking isn't simple; as
41 long as the api exported by the object is simple, handling the nasty
42 crap internally shouldn't be an issue.
43
44
45 > Also, with a long running portage
46 > process and badly written code, it'd be quite easy to run out of fds...
47 All locking code (stable included) has this potential though- it's not
48 something limited to this implementation.
49
50 > > > +       def __del__(self):
51 > > > +               # alright, it's 5:45am, yes this is weird code.
52 > > > +               try:
53 > > > +                       if self.fd != None:
54 > > > +                               self.release_read_lock()
55 > > > +               finally:
56 > > > +                       if self.fd != None:
57 > > > +                               os.close(self.fd)
58 > > >
59 > > > Allowing an exception to be raised during __del__ is not a good thing...
60 > >
61 > No traceback though... Something like the following (on one line):
62 >
63 > Exception exceptions.Exception:
64 > <exceptions.Exception instance at 0x2aaaaab2db48> in
65 > <bound method foo.__del__ of <__main__.foo instance at 0x2aaaaab2d5f0>>
66 > ignored
67 >
68 > I guess it's enough to point out where debugging needs to start...
69 Exactly. Misbehaving __del__ code can be fun to track, usually prefer
70 the ugly complaints dumped to term to silence.
71
72 > > > +++ ./pym/portage_modules.py    2005-11-16 01:56:28.269687832 -0500
73 > > > +               # * ferringb|afk hit it at some point, sure of that
74 > > > +               # but no idea how, so commenting out to see if things break...
75 > > > +               # except AttributeError, e:
76 > > > +               #       raise FailedImport(name, e)
77 > > >
78 > > > I can't see how either, but comments like these don't really help. Please
79 > > > leave them out unless they describe how the code works.
80 > > Leaving comments regarding "weirdness occured via here, but went away"
81 > > isn't a bad thing- if it reappears, you know that at least it was hit
82 > > once prior.
83 > >
84 > > I know how it was hit also (just dawned on me how it would happen)-
85 > > echo $'import os;print os.dar' > test.py
86 > > python -c'import test'
87 > >
88 > > AttributeError...
89 > >
90 > > Should nuke the module imo in that case still, since it's a failed
91 > > import.
92 >
93 > Yep, so it shouldn't be commented then - which is my point. If code is to be
94 > going into stable, things like this should be resolved prior to.
95
96 Code needs further work if it's going into stable imo.
97
98 Reason I wanted the code split off from 3.0 and backported was to show
99 a file based approach rather then trying to do scans at load up.
100
101
102 > > > +       def query_plugins(self, plugin_type=None, locking=True, raw=False):
103 > > >
104 > > > Not sure about allowing the external callers to disable locking... That
105 > > > should only be used by register and deregister, right? Perhaps breaking
106 > > > the unlocked version into a "protected" method?
107 > >
108 > > Debatable; any protection used people can stomp right past. Not much
109 > > for the "sitting on the porch with the shotgun" approach.
110 > >
111 > > Not really seeing the gain in hiding the locking tbh; shoving it into
112 > > the instance namespace would require locking the attribute to maintain
113 > > thread safety; the only gain is just an extra hoop to jump through if
114 > > I'm after doing something stupid. :)
115 >
116 > Should query_plugins be called without no locking in place? In the code so
117 > far it is only called when a lock is being maintained outside of the method
118 > call. I'm not sure what you are referring to with regard to instance
119 > namespace. To be clear, I'm suggesting:
120 >
121 > def register_plugins(...):
122 > lock_stuff()
123 > do_stuff()
124 > internal_query_plugins()
125 > unlock_stuff()
126 >
127 > def query_plugins(...):
128 > lock_stuff()
129 > internal_query_plugins()
130 > unlock_stuff()
131 >
132 > def internal_query_plugins(...):
133 > do_stuff()
134 >
135 > There's no namespace stuff there...
136 Doable in that route; was assuming you meant to shove the lock into
137 the instance.
138
139 > > > +                                       d[x[:-len_exten]] = dict([(y, dict(c.items(y))) for y in c.sections()])
140 > > >
141 > > > Same here on the 2.2 bit... Although that's pretty hard to read either way.
142 > > Prefer a map call? :)
143 >
144 > Whatever is most readable (and supported).
145 Bit from above is supported in 2.2 and up. Probably early, offhand.
146 The example is a bit out of context, since the len_exten is defined
147 above, and clear what it's doing- the dict arg instantiation might be
148 a bit weird if people don't know that you can (essentially) pass a
149 zipped set of arrays to dict, which it'll convert into key->vals...
150 that said, chunking out a replacement of 3 lines seems a bit pointless
151 imo.
152
153 Dunno. for stable, can go either way, for 3.0, questionable since
154 it's a common enough trick.
155
156 > > > +               finally:
157 > > > +                       if locking:
158 > > > +                               plug_lock.release_read_lock()
159 > > > +               if plugin_type != None:
160 > > > +                       return d[plugin_type]
161 > > > +               return d
162 > > >
163 > > > Not sure about having different return types...
164 > > Agreed.
165 >
166 > So one method for querying what plugin types are available and another for
167 > querying plugins of a given type?
168 Or one for querying the full tree, one for pulling a specific
169 branch...
170
171 > > > +registry = None
172 > > > +from portage.util.currying import pre_curry, pretty_docs
173 > > > +
174 > > > +def proxy_it(method, *a, **kw):
175 > > > +       global registry
176 > > > +       if registry == None:
177 > > > +               registry = GlobalPluginRegistry()
178 > > > +       return getattr(registry, method)(*a, **kw)
179 > > > +
180 > > > +for x in ["register", "deregister", "query_plugins", "get_plugin"]:
181 > > > +       v = pre_curry(proxy_it, x)
182 > > > +       doc = getattr(GlobalPluginRegistry, x).__doc__
183 > > > +       if doc == None:
184 > > > +               doc = ''
185 > > > +       else:
186 > > > +               # do this so indentation on pydoc __doc__ is sane
187 > > > +               doc = "\n".join(map(lambda x:x.lstrip(), doc.split("\n"))) +"\n"
188 > > > +       doc += "proxied call to module level registry instances %s method" % x
189 > > > +       globals()[x] = pretty_docs(v, doc)
190 > > > +
191 > > > +del x, v, proxy_it, doc
192 > > >
193 > > > What's this currying and pretty_docs stuff?
194 > > currying == arg binding to a func- in this case, proxy_it gets first
195 > > arg of the attr requested; the proxy_it func instantiates a
196 > > GlobalPluginRegistry instance if it's missing.
197 >
198 > This stuff seems to be missing from the patch then.
199 Offhand, looking over the split patch, this won't even work.
200 The from portage.util.currying import ... is still savior namespace.
201
202 Curious, qualms about using a massively cleaned up version of this?
203 The reason I wanted this seperated out and backported was to go with
204 an actual registry, and abuse the existing code- specific complaints
205 with that route, beyond patch sucking?
206 ~harring

Replies

Subject Author
Re: [gentoo-portage-dev] Plugin backport PATCH (1/2)/(2/2) Jason Stubbs <jstubbs@g.o>