Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 5 Sep 2002 09:27:33 -0700 (PDT)
From:      Julian Elischer <julian@elischer.org>
To:        Terry Lambert <tlambert2@mindspring.com>
Cc:        Giorgos Keramidas <keramida@ceid.upatras.gr>, Bruce Evans <bde@zeta.org.au>, arch@FreeBSD.ORG
Subject:   Re: Process/thread states.
Message-ID:  <Pine.BSF.4.21.0209050854590.36697-100000@InterJet.elischer.org>
In-Reply-To: <3D770DF1.623A84B0@mindspring.com>

next in thread | previous in thread | raw e-mail | index | archive | help


On Thu, 5 Sep 2002, Terry Lambert wrote:

> Julian Elischer wrote:
> > the state of being on the sleep queue can occur in parallel with the other
> > states, e.g. on the run queue, or running or on the suspend queue.
> > Sleeping is really only being on the sleep queue without any other
> > state taking precedence.
> > This means that to truely represent the queue and run states of a thread
> > the state variable consists of a state field with several values
> > and a separate bit that represents the background reality of it also being
> > on the sleep queue, and thus able to be awoken. There are also
> > possibilities that a thread having been swapped out (as part of a process)
> > that must also be kept orthogonal to whether the thread is suspended or
> > sleeping or runnable. if this really does turn out to be the case
> > REAL thread state tracking requires 3 orthognal tests. For this reason I
> > want touse a macro to allow,
> > 1/ experimentation on how thisis best done
> > 2/ hiding of the sometime messy tests with meaningful named abstractions.
> 
> To me this seems to introduce a bitfield/other-field-contents
> synchroniztion problem that wasn't there before.  Effectively,
> you are saying that the value of the bit follows the change from
> NULL-to-non-NULL and non-NULL-to-NULL.  There is an implicit race
> that happens, unless both objects are protected by the same mutex.
> 
> In the case of a direct compare with the object itself, then there
> is not race in the following field.
> 
> Bruce made the point that debugging such macros is arbitrarily
> hard.  This is true because, even if you ensure that the order
> of operation is always set A/set B, unset B/unset A, your macro
> used in the comparator functions has to be consistent... and also
> used consistently.  In other words, there is an implied semantic
> on use, when there is a combined state comparison, which is not
> explicit in the transitions between states themselves (assuming
> your state is represented by the "and" or "xor" of two pieces of
> information).  There are several cases in the code in question
> where there are comparisons which fail.  Specifically, the negation
> of the macro operation requires inverse ordering of the compared
> values, in order to avoid introducing a "follower" race window.

state changes are all done under the sched lock

> 
> It's possible to get this right, but it also means a lot of work;
> IMO, it's not worth the implied semantics to get the simplified
> "if" statements; you would basically need two macros, with one
> order for the assertion, and the other order for the assertion
> of the negation.  The compiler can't enforce the use of the
> correct semantics here, so it's easy for programmers to make
> errors, e.g.:
> 
> 	if (!ASSERTION_STATEMENT(x))
> 
> vs.
> 
> 	if (NEGATION_STATEMENT(x))
> 
> that end up introducing incredibly small -- but still there --
> race windows in the code, which will pop up as unrepeatable
> errors suffered by one or two users, which "disappear" when a
> printf() (or other timing-changing function) is introduced,
> etc..
> 
> To my mind, it is better to multiply the states explicitly,
> and use the product of the multiplication in the code.  For
> anything more than a two-by-two, this introduces more states
> (2x3 := 5->6, 4x5 := 9->20, etc.).  This means that a lot of
> care will have to be taken in the code.  On the plus side, it
> means that someone who comes after you is not going to screw
> things up accidently by missing some required semantics that
> the compiler is unable to enforce.  It's also possible to
> reduce the total number of states by eliminating "impossible"
> states (e.g. with a Karnot map), so the increase in total
> states is not necessarily really any greater than it would
> have been the other way.
> 
> 
> > > Neither one of these works properly if there is more than
> > > one bit hidden in either of these; you have to get a lot more
> > > complicated:
> > 
> > this one could be real:
> >  if ((td->td_wchan == 0) && (td->td_state & TD_ST_MASK == TDS_UNQUEUED) &&
> >       ((td->td_state & TDS_SWAPPED) == 0))
> >         setrunnable(td);
> > 
> > I'd rather make it
> > 
> > if (td_on_sleepq_only(td) && !td_is_swapped(td))
> >         setrunnable(td);
> > 
> > Isn't that somewhat easier to understand?
> 
> Yes.  But it falls into the negation case race problem.  A similar
> condition elsewhere:
> 
> 	if (!td_on_sleepq_only(td) || td_is_swapped(td))
> 		do_something(td);
> 
> is actually a potential race, unless it's:
> 
> 	if(td_is_swapped(td) || !td_on_sleepq_only(td))
> 
> ...but it's worse than that.  By compressing the state compare into a
> single macro, you lock the order of operation.  The only legitimate
> way to do this is if you have a mutex that protects the contents of
> td->td_state, since there is no way that you can guarantee that both
> elements will be updated atomically otherwise.  The order of the
> update becomes important, if the condition is not exactly equal to
> the example.  Further, I would argue that the use of a lowercase
> macro obfuscates the idea of atmoicity -- atomicity is implied, but
> not actually guaranteed over (possible) kernel preemption (e.g. a
> thread on another CPU explcitly killing the thread in question
> between one compare of td->td_state contents,  and the other).
> 
> 
> > > That *still* doesn't work if, say, TDS_XXX is a superset of the
> > > bits in TDS_SUSPENDED.
> > 
> > > I personally dislike the idea omf combined bits, without explicit
> > > equality tests.
> > 
> > I have no idea what you mean by that.
> 
> When you defined your manifest constants, you defined them as the
> logical OR of a number of bits in your enumerated type declaration;
> this means that one set of bits could be a subset of another.  Even
> if you have a mask and two flag bits on a value, this is a problem.
> To make something up:
> 
> 	enum {
> 		foo = 75 | 0x10000000,
> 		fum = 75 | 0x10000000 | 0x20000000,
> 		...
> 	}
> 
> This is actually *likely*, given that people will tend to add bits
> as time goes on.
> 
> 
> > > The problem that's trying to be solved here is the one of how
> > > do you represent a combinatorial state in a single comparison,
> > > and the equivalency of one combinatorial state and another for
> > > the purposes of whether or not some code should be run (as in
> > > this specific case).
> > >
> > > The reality is probably that this state should be explicitly
> > > multiplied out in the code, so that it becomes a single value
> > > compare.
> > 
> > sometimes you want otherwise.. e.g.
> > What is the base state REGARDLESS of whether it is on the sleep queue.
> 
> I would argue that the compare of the sleep queue entry pointer
> to NULL is the *ideal* was to get this information.  8-).
> 
> -- Terry
> 


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-arch" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?Pine.BSF.4.21.0209050854590.36697-100000>