Re: [PATCH net-next v6 10/11] net: sched: atomically check-allocate action

2018-08-13 Thread Vlad Buslov


On Fri 10 Aug 2018 at 21:45, Cong Wang  wrote:
> On Fri, Aug 10, 2018 at 3:29 AM Vlad Buslov  wrote:
>>
>> Approach you suggest is valid, but has its own trade-offs:
>>
>> - As you noted, lock granularity becomes coarse-grained due to per-netns
>> scope.
>
> Sure, you acquire idrinfo->lock too, the only difference is how long
> you take it.
>
> The bottleneck of your approach is the same, also you take idrinfo->lock
> twice, so the contention is heavier.
>
>
>>
>> - I am not sure it is possible to call idr_replace() without obtaining
>> idrinfo->lock in this particular case. Concurrent delete of action with
>> same id is possible and, according to idr_replace() description,
>> unlocked execution is not supported for such use-case:
>
> But we can hold its refcnt before releasing idrinfo->lock, so
> idr_replace() can't race with concurrent delete.

Yes, for concurrent delete case I agree. Action is removed from idr only
when last reference is released and, in case of existing action update,
init holds a reference.

What about case when multiple task race to update the same existing
action? I assume idr_replace() can be used for such case, but what would
be the algorithm in case init replaced some other action, and not the
action it actually copied before calling idr_replace()?

>
>
>>
>> - High rate or replace request will generate a lot of unnecessary memory
>> allocations and deallocations.
>>
>
> Yes, this is literally how RCU works, always allocate and copy,
> release upon error.
>
> Also, if this is really a problem, we have SLAB_TYPESAFE_BY_RCU
> too. ;)

Current action update implementation is in-place, so there is no "copy"
stage, besides members of some actions that are RCU-pointers. But I
guess it makes sense if your goal is to refactor all actions to be
updated with RCU.


Re: [PATCH net-next v6 10/11] net: sched: atomically check-allocate action

2018-08-10 Thread Cong Wang
On Fri, Aug 10, 2018 at 3:29 AM Vlad Buslov  wrote:
>
> Approach you suggest is valid, but has its own trade-offs:
>
> - As you noted, lock granularity becomes coarse-grained due to per-netns
> scope.

Sure, you acquire idrinfo->lock too, the only difference is how long
you take it.

The bottleneck of your approach is the same, also you take idrinfo->lock
twice, so the contention is heavier.


>
> - I am not sure it is possible to call idr_replace() without obtaining
> idrinfo->lock in this particular case. Concurrent delete of action with
> same id is possible and, according to idr_replace() description,
> unlocked execution is not supported for such use-case:

But we can hold its refcnt before releasing idrinfo->lock, so
idr_replace() can't race with concurrent delete.


>
> - High rate or replace request will generate a lot of unnecessary memory
> allocations and deallocations.
>

Yes, this is literally how RCU works, always allocate and copy,
release upon error.

Also, if this is really a problem, we have SLAB_TYPESAFE_BY_RCU
too. ;)


Re: [PATCH net-next v6 10/11] net: sched: atomically check-allocate action

2018-08-10 Thread Vlad Buslov


On Thu 09 Aug 2018 at 23:43, Cong Wang  wrote:
> On Wed, Aug 8, 2018 at 5:06 AM Vlad Buslov  wrote:
>>
>>
>> On Wed 08 Aug 2018 at 01:20, Cong Wang  wrote:
>> > On Thu, Jul 5, 2018 at 7:24 AM Vlad Buslov  wrote:
>> >>
>> >> Implement function that atomically checks if action exists and either 
>> >> takes
>> >> reference to it, or allocates idr slot for action index to prevent
>> >> concurrent allocations of actions with same index. Use EBUSY error pointer
>> >> to indicate that idr slot is reserved.
>> >
>> > A dumb question:
>> >
>> > How could "concurrent allocations of actions with same index" happen
>> > as you already take idrinfo->lock for the whole
>> > tcf_idr_check_alloc()??
>>
>> I guess my changelog is not precise enough in this description.
>> Let look into sequence of events of initialization of new action:
>> 1) tcf_idr_check_alloc() is called by action init.
>> 2) idrinfo->lock is taken.
>> 3) Lookup in idr is performed to determine if action with specified
>> index already exists.
>> 4) EBUSY pointer is inserted to indicate that id is taken.
>> 5) idrinfo->lock is released.
>> 6) tcf_idr_check_alloc() returns to action init code.
>> 7) New action is allocated and initialized.
>> 8) tcf_idr_insert() is called.
>> 9) idrinfo->lock is taken.
>> 10) EBUSY pointer is substituted with pointer to new action.
>> 11) idrinfo->lock is released.
>> 12) tcf_idr_insert() returns.
>>
>> So in this case "concurrent allocations of actions with same index"
>> means not the allocation with same index during tcf_idr_check_alloc(),
>> but during the period when idrinfo->lock was released(6-8).
>
> Yes but it is unnecessary:
>
> a) When adding a new action, you can actually allocate and init it before
> touching idrinfo, therefore the check and insert can be done in one step
> instead of breaking down it into multiple steps, which means you can
> acquire idrinfo->lock once.
>
> b) When updating an existing action, it is slightly complicated.
> However, you can still allocate a new one first, then find the old one
> and copy it into the new one and finally replace it.
>
> In summary, we can do the following:
>
> 1. always allocate a new action
> 2. acquire idrinfo->lock
> 3a. if it is an add operation: allocate a new ID and insert the new action
> 3b. if it is a replace operation: find the old one with ID, copy it into the
> new one and replace it
> 4. release idrinfo->lock
> 5. If 3a or 3b fails, free the allocation. Otherwise succeed.
>
> I know, the locking scope is now per netns rather than per action,
> but this can be optimized for replacing, you can hold the old action
> and then release the idrinfo->lock, as idr_replace() later doesn't
> require idrinfo->lock AFAIK.
>
> Is there anything I miss here?

Approach you suggest is valid, but has its own trade-offs:

- As you noted, lock granularity becomes coarse-grained due to per-netns
scope.

- I am not sure it is possible to call idr_replace() without obtaining
idrinfo->lock in this particular case. Concurrent delete of action with
same id is possible and, according to idr_replace() description,
unlocked execution is not supported for such use-case:

/**
 * idr_replace() - replace pointer for given ID.
 * @idr: IDR handle.
 * @ptr: New pointer to associate with the ID.
 * @id: ID to change.
 *
 * Replace the pointer registered with an ID and return the old value.
 * This function can be called under the RCU read lock concurrently with
 * idr_alloc() and idr_remove() (as long as the ID being removed is not
 * the one being replaced!).
 *
 * Returns: the old value on success.  %-ENOENT indicates that @id was not
 * found.  %-EINVAL indicates that @ptr was not valid.
 */

- High rate or replace request will generate a lot of unnecessary memory
allocations and deallocations.

>
>
>>
>> >
>> > For me, it should be only one allocation could succeed, all others
>> > should fail.
>>
>> Correct! And this change is made specifically to enforce that rule.
>>
>> Otherwise, multiple processes could try to create new action with same
>> id at the same time, and all processes that executed 3, before any
>> process reached 10, will "succeed" by overwriting each others action in
>> idr. (and leak memory while doing so)
>
> I know but again it doesn't look necessary to achieve a same goal.
>
>
>>
>> >
>> > Maybe you are trying to prevent others treat it like existing one,
>> > but in that case you can just hold the idinfo->lock for all idr operations.
>> >
>> > And more importantly, upper layer is able to tell it is a creation or
>> > just replace, you don't have to check this in this complicated way.
>> >
>> > IOW, all of these complicated code should not exist.
>>
>> Original code was simpler and didn't involve temporary EBUSY pointer.
>> This change was made according to Jiri's request. He wanted to have
>> unified API to be used by all actions and suggested this approach
>> specifically.
>
> I will work on this, as this is aligned to my work to make
> it 

Re: [PATCH net-next v6 10/11] net: sched: atomically check-allocate action

2018-08-09 Thread Cong Wang
On Wed, Aug 8, 2018 at 5:06 AM Vlad Buslov  wrote:
>
>
> On Wed 08 Aug 2018 at 01:20, Cong Wang  wrote:
> > On Thu, Jul 5, 2018 at 7:24 AM Vlad Buslov  wrote:
> >>
> >> Implement function that atomically checks if action exists and either takes
> >> reference to it, or allocates idr slot for action index to prevent
> >> concurrent allocations of actions with same index. Use EBUSY error pointer
> >> to indicate that idr slot is reserved.
> >
> > A dumb question:
> >
> > How could "concurrent allocations of actions with same index" happen
> > as you already take idrinfo->lock for the whole
> > tcf_idr_check_alloc()??
>
> I guess my changelog is not precise enough in this description.
> Let look into sequence of events of initialization of new action:
> 1) tcf_idr_check_alloc() is called by action init.
> 2) idrinfo->lock is taken.
> 3) Lookup in idr is performed to determine if action with specified
> index already exists.
> 4) EBUSY pointer is inserted to indicate that id is taken.
> 5) idrinfo->lock is released.
> 6) tcf_idr_check_alloc() returns to action init code.
> 7) New action is allocated and initialized.
> 8) tcf_idr_insert() is called.
> 9) idrinfo->lock is taken.
> 10) EBUSY pointer is substituted with pointer to new action.
> 11) idrinfo->lock is released.
> 12) tcf_idr_insert() returns.
>
> So in this case "concurrent allocations of actions with same index"
> means not the allocation with same index during tcf_idr_check_alloc(),
> but during the period when idrinfo->lock was released(6-8).

Yes but it is unnecessary:

a) When adding a new action, you can actually allocate and init it before
touching idrinfo, therefore the check and insert can be done in one step
instead of breaking down it into multiple steps, which means you can
acquire idrinfo->lock once.

b) When updating an existing action, it is slightly complicated.
However, you can still allocate a new one first, then find the old one
and copy it into the new one and finally replace it.

In summary, we can do the following:

1. always allocate a new action
2. acquire idrinfo->lock
3a. if it is an add operation: allocate a new ID and insert the new action
3b. if it is a replace operation: find the old one with ID, copy it into the
new one and replace it
4. release idrinfo->lock
5. If 3a or 3b fails, free the allocation. Otherwise succeed.

I know, the locking scope is now per netns rather than per action,
but this can be optimized for replacing, you can hold the old action
and then release the idrinfo->lock, as idr_replace() later doesn't
require idrinfo->lock AFAIK.

Is there anything I miss here?


>
> >
> > For me, it should be only one allocation could succeed, all others
> > should fail.
>
> Correct! And this change is made specifically to enforce that rule.
>
> Otherwise, multiple processes could try to create new action with same
> id at the same time, and all processes that executed 3, before any
> process reached 10, will "succeed" by overwriting each others action in
> idr. (and leak memory while doing so)

I know but again it doesn't look necessary to achieve a same goal.


>
> >
> > Maybe you are trying to prevent others treat it like existing one,
> > but in that case you can just hold the idinfo->lock for all idr operations.
> >
> > And more importantly, upper layer is able to tell it is a creation or
> > just replace, you don't have to check this in this complicated way.
> >
> > IOW, all of these complicated code should not exist.
>
> Original code was simpler and didn't involve temporary EBUSY pointer.
> This change was made according to Jiri's request. He wanted to have
> unified API to be used by all actions and suggested this approach
> specifically.

I will work on this, as this is aligned to my work to make
it RCU-complete.


Re: [PATCH net-next v6 10/11] net: sched: atomically check-allocate action

2018-08-08 Thread Vlad Buslov


On Wed 08 Aug 2018 at 01:20, Cong Wang  wrote:
> On Thu, Jul 5, 2018 at 7:24 AM Vlad Buslov  wrote:
>>
>> Implement function that atomically checks if action exists and either takes
>> reference to it, or allocates idr slot for action index to prevent
>> concurrent allocations of actions with same index. Use EBUSY error pointer
>> to indicate that idr slot is reserved.
>
> A dumb question:
>
> How could "concurrent allocations of actions with same index" happen
> as you already take idrinfo->lock for the whole
> tcf_idr_check_alloc()??

I guess my changelog is not precise enough in this description.
Let look into sequence of events of initialization of new action:
1) tcf_idr_check_alloc() is called by action init.
2) idrinfo->lock is taken.
3) Lookup in idr is performed to determine if action with specified
index already exists.
4) EBUSY pointer is inserted to indicate that id is taken.
5) idrinfo->lock is released.
6) tcf_idr_check_alloc() returns to action init code.
7) New action is allocated and initialized.
8) tcf_idr_insert() is called.
9) idrinfo->lock is taken.
10) EBUSY pointer is substituted with pointer to new action.
11) idrinfo->lock is released.
12) tcf_idr_insert() returns.

So in this case "concurrent allocations of actions with same index"
means not the allocation with same index during tcf_idr_check_alloc(),
but during the period when idrinfo->lock was released(6-8).

>
> For me, it should be only one allocation could succeed, all others
> should fail.

Correct! And this change is made specifically to enforce that rule.

Otherwise, multiple processes could try to create new action with same
id at the same time, and all processes that executed 3, before any
process reached 10, will "succeed" by overwriting each others action in
idr. (and leak memory while doing so)

>
> Maybe you are trying to prevent others treat it like existing one,
> but in that case you can just hold the idinfo->lock for all idr operations.
>
> And more importantly, upper layer is able to tell it is a creation or
> just replace, you don't have to check this in this complicated way.
>
> IOW, all of these complicated code should not exist.

Original code was simpler and didn't involve temporary EBUSY pointer.
This change was made according to Jiri's request. He wanted to have
unified API to be used by all actions and suggested this approach
specifically.



Re: [PATCH net-next v6 10/11] net: sched: atomically check-allocate action

2018-08-07 Thread Cong Wang
On Thu, Jul 5, 2018 at 7:24 AM Vlad Buslov  wrote:
>
> Implement function that atomically checks if action exists and either takes
> reference to it, or allocates idr slot for action index to prevent
> concurrent allocations of actions with same index. Use EBUSY error pointer
> to indicate that idr slot is reserved.

A dumb question:

How could "concurrent allocations of actions with same index" happen
as you already take idrinfo->lock for the whole tcf_idr_check_alloc()??

For me, it should be only one allocation could succeed, all others
should fail.

Maybe you are trying to prevent others treat it like existing one,
but in that case you can just hold the idinfo->lock for all idr operations.

And more importantly, upper layer is able to tell it is a creation or
just replace, you don't have to check this in this complicated way.

IOW, all of these complicated code should not exist.


[PATCH net-next v6 10/11] net: sched: atomically check-allocate action

2018-07-05 Thread Vlad Buslov
Implement function that atomically checks if action exists and either takes
reference to it, or allocates idr slot for action index to prevent
concurrent allocations of actions with same index. Use EBUSY error pointer
to indicate that idr slot is reserved.

Implement cleanup helper function that removes temporary error pointer from
idr. (in case of error between idr allocation and insertion of newly
created action to specified index)

Refactor all action init functions to insert new action to idr using this
API.

Reviewed-by: Marcelo Ricardo Leitner 
Signed-off-by: Vlad Buslov 
Signed-off-by: Jiri Pirko 
---
Changes from V1 to V2:
- Remove unique idr insertion function. Change original idr insert to do
  the same thing.
- Refactor action check-alloc code into standalone function.

 include/net/act_api.h  |  3 ++
 net/sched/act_api.c| 92 --
 net/sched/act_bpf.c| 11 --
 net/sched/act_connmark.c   | 10 +++--
 net/sched/act_csum.c   | 11 --
 net/sched/act_gact.c   | 11 --
 net/sched/act_ife.c|  6 ++-
 net/sched/act_ipt.c| 13 ++-
 net/sched/act_mirred.c | 16 ++--
 net/sched/act_nat.c| 11 --
 net/sched/act_pedit.c  | 12 --
 net/sched/act_police.c |  9 -
 net/sched/act_sample.c | 11 --
 net/sched/act_simple.c | 11 +-
 net/sched/act_skbedit.c| 11 +-
 net/sched/act_skbmod.c | 11 +-
 net/sched/act_tunnel_key.c |  9 -
 net/sched/act_vlan.c   | 17 -
 18 files changed, 216 insertions(+), 59 deletions(-)

diff --git a/include/net/act_api.h b/include/net/act_api.h
index b9ed2b8256a5..8090de2edab7 100644
--- a/include/net/act_api.h
+++ b/include/net/act_api.h
@@ -154,6 +154,9 @@ int tcf_idr_create(struct tc_action_net *tn, u32 index, 
struct nlattr *est,
   int bind, bool cpustats);
 void tcf_idr_insert(struct tc_action_net *tn, struct tc_action *a);
 
+void tcf_idr_cleanup(struct tc_action_net *tn, u32 index);
+int tcf_idr_check_alloc(struct tc_action_net *tn, u32 *index,
+   struct tc_action **a, int bind);
 int tcf_idr_delete_index(struct tc_action_net *tn, u32 index);
 int __tcf_idr_release(struct tc_action *a, bool bind, bool strict);
 
diff --git a/net/sched/act_api.c b/net/sched/act_api.c
index eefe8c2fe667..9511502e1cbb 100644
--- a/net/sched/act_api.c
+++ b/net/sched/act_api.c
@@ -303,7 +303,9 @@ static bool __tcf_idr_check(struct tc_action_net *tn, u32 
index,
 
spin_lock(>lock);
p = idr_find(>action_idr, index);
-   if (p) {
+   if (IS_ERR(p)) {
+   p = NULL;
+   } else if (p) {
refcount_inc(>tcfa_refcnt);
if (bind)
atomic_inc(>tcfa_bindcnt);
@@ -371,7 +373,6 @@ int tcf_idr_create(struct tc_action_net *tn, u32 index, 
struct nlattr *est,
 {
struct tc_action *p = kzalloc(ops->size, GFP_KERNEL);
struct tcf_idrinfo *idrinfo = tn->idrinfo;
-   struct idr *idr = >action_idr;
int err = -ENOMEM;
 
if (unlikely(!p))
@@ -389,20 +390,6 @@ int tcf_idr_create(struct tc_action_net *tn, u32 index, 
struct nlattr *est,
goto err2;
}
spin_lock_init(>tcfa_lock);
-   idr_preload(GFP_KERNEL);
-   spin_lock(>lock);
-   /* user doesn't specify an index */
-   if (!index) {
-   index = 1;
-   err = idr_alloc_u32(idr, NULL, , UINT_MAX, GFP_ATOMIC);
-   } else {
-   err = idr_alloc_u32(idr, NULL, , index, GFP_ATOMIC);
-   }
-   spin_unlock(>lock);
-   idr_preload_end();
-   if (err)
-   goto err3;
-
p->tcfa_index = index;
p->tcfa_tm.install = jiffies;
p->tcfa_tm.lastuse = jiffies;
@@ -412,7 +399,7 @@ int tcf_idr_create(struct tc_action_net *tn, u32 index, 
struct nlattr *est,
>tcfa_rate_est,
>tcfa_lock, NULL, est);
if (err)
-   goto err4;
+   goto err3;
}
 
p->idrinfo = idrinfo;
@@ -420,8 +407,6 @@ int tcf_idr_create(struct tc_action_net *tn, u32 index, 
struct nlattr *est,
INIT_LIST_HEAD(>list);
*a = p;
return 0;
-err4:
-   idr_remove(idr, index);
 err3:
free_percpu(p->cpu_qstats);
 err2:
@@ -437,11 +422,78 @@ void tcf_idr_insert(struct tc_action_net *tn, struct 
tc_action *a)
struct tcf_idrinfo *idrinfo = tn->idrinfo;
 
spin_lock(>lock);
-   idr_replace(>action_idr, a, a->tcfa_index);
+   /* Replace ERR_PTR(-EBUSY) allocated by tcf_idr_check_alloc */
+   WARN_ON(!IS_ERR(idr_replace(>action_idr, a, a->tcfa_index)));
spin_unlock(>lock);
 }
 EXPORT_SYMBOL(tcf_idr_insert);
 
+/* Cleanup idr index that was allocated but not initialized. */
+
+void tcf_idr_cleanup(struct tc_action_net *tn, u32 index)
+{
+   struct