* synchronization. Use atomic reference counter to be concurrency-safe.
         */
        refcount_t refcnt;
+       bool deleted;
 };
 
 static const struct rhashtable_params mask_ht_params = {
        if (!refcount_dec_and_test(&f->refcnt))
                return;
 
+       WARN_ON(!f->deleted);
+
        if (tcf_exts_get_net(&f->exts))
                tcf_queue_work(&f->rwork, fl_destroy_filter_work);
        else
        return f;
 }
 
-static bool __fl_delete(struct tcf_proto *tp, struct cls_fl_filter *f,
-                       struct netlink_ext_ack *extack)
+static int __fl_delete(struct tcf_proto *tp, struct cls_fl_filter *f,
+                      bool *last, struct netlink_ext_ack *extack)
 {
        struct cls_fl_head *head = fl_head_dereference(tp);
        bool async = tcf_exts_get_net(&f->exts);
-       bool last;
 
+       *last = false;
+
+       if (f->deleted)
+               return -ENOENT;
+
+       f->deleted = true;
+       rhashtable_remove_fast(&f->mask->ht, &f->ht_node,
+                              f->mask->filter_ht_params);
        idr_remove(&head->handle_idr, f->handle);
        list_del_rcu(&f->list);
-       last = fl_mask_put(head, f->mask, async);
+       *last = fl_mask_put(head, f->mask, async);
        if (!tc_skip_hw(f->flags))
                fl_hw_destroy_filter(tp, f, extack);
        tcf_unbind_filter(tp, &f->res);
        __fl_put(f);
 
-       return last;
+       return 0;
 }
 
 static void fl_destroy_sleepable(struct work_struct *work)
        struct cls_fl_head *head = fl_head_dereference(tp);
        struct fl_flow_mask *mask, *next_mask;
        struct cls_fl_filter *f, *next;
+       bool last;
 
        list_for_each_entry_safe(mask, next_mask, &head->masks, list) {
                list_for_each_entry_safe(f, next, &mask->filters, list) {
-                       if (__fl_delete(tp, f, extack))
+                       __fl_delete(tp, f, &last, extack);
+                       if (last)
                                break;
                }
        }
 
        refcount_inc(&fnew->refcnt);
        if (fold) {
+               /* Fold filter was deleted concurrently. Retry lookup. */
+               if (fold->deleted) {
+                       err = -EAGAIN;
+                       goto errout_hw;
+               }
+
                fnew->handle = handle;
 
                err = rhashtable_insert_fast(&fnew->mask->ht, &fnew->ht_node,
                                       fold->mask->filter_ht_params);
                idr_replace(&head->handle_idr, fnew, fnew->handle);
                list_replace_rcu(&fold->list, &fnew->list);
+               fold->deleted = true;
 
                if (!tc_skip_hw(fold->flags))
                        fl_hw_destroy_filter(tp, fold, NULL);
 {
        struct cls_fl_head *head = fl_head_dereference(tp);
        struct cls_fl_filter *f = arg;
+       bool last_on_mask;
+       int err = 0;
 
-       rhashtable_remove_fast(&f->mask->ht, &f->ht_node,
-                              f->mask->filter_ht_params);
-       __fl_delete(tp, f, extack);
+       err = __fl_delete(tp, f, &last_on_mask, extack);
        *last = list_empty(&head->masks);
        __fl_put(f);
 
-       return 0;
+       return err;
 }
 
 static void fl_walk(struct tcf_proto *tp, struct tcf_walker *arg,