> From: dev [mailto:dev-bounces at dpdk.org] On Behalf Of Neil Horman
> Sent: Friday, September 26, 2014 2:40 PM
> To: Wodkowski, PawelX
> Cc: dev at dpdk.org
> Subject: Re: [dpdk-dev] [PATCH v2] Change alarm cancel function to
> thread-safe:
>
> On Fri, Sep 26, 2014 at 12:37:54PM +0000, Wodkowski, PawelX wrote:
> > > So basically cancel() just set ALARM_CANCELLED and leaves actual alarm
> > > deletion to the callback()?
> > > That was the thought, yes.
> > >
> > > > I think it is doable - but I don't see any real advantage with that
> > > > approach.
> > > > Yes, code will become a bit simpler, as we'll have one point when we
> > > > remove
> > > alarm from the list.
> > > Yes, that would be the advantage, that the code would be much simpler.
> > >
> > > > But from other side, imagine such simple test-case:
> > > >
> > > > for (i = 0; i < 0x100000; i++) {
> > > > rte_eal_alarm_set(ONE_MIN, cb_func, (void *)i);
> > > > rte_eal_alarm_cancel(cb_func, (void *)i);
> > > > }
> > > >
> > > > We'll endup with 1M of cancelled, but still not removed entries in the
> > > alarm_list.
> > > > With current implementation that means - few MBs of wasted memory,
> > > Thats correct, and the tradeoff to choose between. Do you want simpler
> > > code
> > > that is easier to maintain, or do you want a high speed cancel and set
> > > operation. I'm not aware of all the use cases, but I have a hard time
> > > seeing
> > > a use case in which the in-flight alarm list grows unboundedly large,
> > > which in
> > > my mind mitigates the risk of deferred removal, but I'm perfectly willing
> > > to
> > > believe that there are use cases which I'm not aware of.
After executing example above - from user perspective there is no active alarms
in the system at all.
Though in fact alarm_list contains 1M entries.
> > >
> > > > plus very slow set() and cancel(), as they'll have to traverse all
> > > > entries in the
> > > list.
> > > > And all that - for empty from user perspective alarm_list
> > > > So I still prefer Michal's way.
> > > > After all, it doesn't look that complicated to me.
> > > Except that the need for Michals fix arose from the fact that we have two
> > > free
> > > locations that might both get called depending on the situation. Thats
> > > what I'm
> > > trying to address here, the complexity itself, rather than the fix (which
> > > I
> > > agree is perfectly valid).
Well, I believe his fix addresses all the open issues, no?
Another thing, as Pawel pointed in another mail, your fix doesn't address
properly the situation when
we have a racing alarm_cancel(cb_func) and alarm cb_func rearming itself.
While the original patch does.
> > >
> > > > BTW, any particular reason you are so negative about pthread_self()?
> > > >
> > > Nothing specifically against it (save for its inverted return code sense,
> > > which
> > > made it difficult for me to parse when reviewing). Its more the
> > > complexity
> > > itself in the alarm cancel and callback routine that I was looking at.
> > > Given
> > > that the origional bug happened because an cancel in a callback might
> > > produce a
> > > double free, I wanted to fix it by simpifying the code, not adding
> > > conditions
> > > which make it more complex.
> > >
> > > You know, looking at it, something else just occured to me. I think this
> > > could
> > > all be fixed without the cancel flag or the pthread_self assignments.
> > > What if
> > > we simply removed the alarm from the list before we called the callback in
> > > rte_eal_alarm_callback()? That way any cancel operation called from
> > > within the
> > > callback would fail, as it wouldn't appear on the list, and the callback
> > > operation would be the only freeing entity? That would let you still
> > > have a
> > > fast set and cancel, and avoid the race. Thoughts? Untested sample patch
> > > below
Hmm, and how it would address any of the problems I mentioned above:
1. The alarm_list still would grow by the repetition of: {alarm_set(x);
alarm_cansel(x);}
2. Race between alarm_cancel(cb_func) and alarm cb_func rearming itself.
?
> > >
> > >
> > > > >
> > > > > It also seems like the alarm api as a whole could use some
> > > > > improvement.
> > > The
> > > > > way its written right now, theres no way to refer to a specific alarm
> > > > > (i.e.
> > > > > cancelation relies on the specification of a function and data
> > > > > pointer, which
> > > > > may refer to multiple timers). Shouldn't rte_eal_alarm_set return an
> > > > > opaque
> > > > > handle to a unique timer instance that can be store by a caller and
> > > > > used to
> > > > > specfically cancel that timer? Thats how both the bsd and linux timer
> > > > > subsystems model timers.
> > > >
> > > > Yeh, alarm API looks a bit unusual.
> > > > Though, I suppose that's subject for another patch/discussion :)
> > > >
> > > Yes, agreed :)
> > >
> >
> > Please read quoted message bellow:
> >
> > > >
> > > >
> > > > His solution *does* eliminate race condition too.
> > > >
> > > I applied his patch. And here is the problem
> > > 1 rte_spinlock_lock(&alarm_list_lk);
> > > 2 while ((ap = LIST_FIRST(&alarm_list)) !=NULL &&
> > > 3 gettimeofday(&now, NULL) == 0 &&
> > > 4 (ap->time.tv_sec < now.tv_sec || (ap->time.tv_sec ==
> > > now.tv_sec &&
> > > 5 ap->time.tv_usec <=
> > > now.tv_usec))){
> > > 6 ap->executing |= ALARM_EXECUTING;
> > > 7 if (likely(!(ap->executing & ALARM_CANCELLED))) {
> > > 8 rte_spinlock_unlock(&alarm_list_lk);
> > > 9 //another thread: rte_alarm_cancel called,
> > > mark this timer
> > > canceled and exit ( THE RACE)
> > > 10 ap->cb_fn(ap->cb_arg); // rte_alarm_set
> > > called
> > > (THE RACE)
> > > 11
> > > 12 rte_spinlock_lock(&alarm_list_lk);
> > > 13 }
> > > 14
> > > 15 rte_spinlock_lock(&alarm_list_lk);
> > > 16 LIST_REMOVE(ap, next);
> > > 17 rte_free(ap);
> > > 18 }
> > >
> > > Imagine
> > >
> > > Thread 1: Thread2
> > > Execute eal_alarm_callback
> > > Lock list at 1 rte_alarm_cancel ->
> > > block on spinlock
> > > ....
> > > Realease lock at line 8 rte_alarm_cancel -> resumes
> > > execution -> it
> > > mark this timer canceled
> > > ap->cb_fn is called at line 10 rte_alarm_cancel exits reporting all
> > > alarms are
> > > canceled and not executing (which is not true!)
> > > rte_alarm_set is called
> > > to rearm itself (THE RACE)
> > >
> > > He only mark timer to * do not execute* but does not assure that it is not
> > > executing while canceling.
> > > Race condition is made by unlocking at line 8 and our solution
> > > workarounds this
> > > by looping until all alarms finish execution then cancel what left after
> > > callback
> > > finish (*including rearmed alarms*).
> > > Real elimination of the race would be by using recursive locking when
> > > *other
> > > thread can't* access the list *while callback* is running but callback
> > > *itself can
> > > by using recursive locking*.
> > >
> > > Maybe I don't see something obvious? :)
>
> I think you're missing the fact that your patch doesn't do what you assert
> above
> either :)
>
> First, lets address rte_alarm_set. There is no notion of "re-arming" in this
> alarm implementation, because theres no ability to refer to a specific alarm
> from the callers perspective. When you call rte_eal_alarm_set you get a new
> alarm every time. So I don't really see a race there. It might not be
> exactly
> the behavior you want, but its not a race, becuase you're not modifying an
> alarm
> in the middle of execution, you're just creating a new alarm, which is safe.
>
> There is a race in what you describe above, insofar as its possible that you
> might call rte_eal_alarm_cancel and return without having canceled all the
> matching alarms. I don't see any clear documentation on what the behavior is
> supposed to be, but if you want to ensure that all matching alarms are
> cancelled
> or complete on return from rte_eal_alarm_cancel, thats perfectly fine (in
> linux
> API parlance, thats usually denoted as a cancel_sync operation).
>
> For that race condition, you're correct, my patch doesn't address it, I see
> that
> now. Though your patch doesn't either. If you call rte_eal_alarm_cancel from
> within a callback function, then, by definition, you can't wait on the
> completion of the active alarm, because thats a deadlock. Its a necessecary
> evil, I grant you, but it means that you can't be guaranteed the cancelled and
> complete (cancel_sync) behavior that you want, at least not with the current
> api. If you want that behavior, you need to do one of two things:
>
> 1) Modify the api to allow callers to individually reference timer instances,
> so
> that when cancelling, we can return an appropriate return code to indicate to
> the caller that this alarm is in-progress. That way you can guarantee the
> caller that the specific alarm that you cancelled is either complete and
> cancelled
> or currently executing. Add an api to expicitly wait on a referenced alarm as
> well. This allows developers to know that, when executing an alarm callback,
> an
> -ECURRENTLYEXECUTING return code is ok, because they are in the currently
> executing context.
>
>
> 2) Understand that the guarantee can't be met, and change nothing, because you
> consider it ok for an in-progress alarm to be ignored when getting cancelled.
>
>
> Neil