2bbfed98a4d8 nfsd: Fix races between nfsd4_cb_release() and nfsd4_shutdown_callback()

1 邮件内容

2bbfed98a4d8 nfsd: Fix races between nfsd4_cb_release() and nfsd4_shutdown_callback() 邮件:

讨论此补丁的相关邮件: nfsd: radix tree warning in nfs4_put_stid and kernel panic

2 依赖补丁

4.19等低版本合入此补丁可能还要合入前置补丁12357f1b2c8e nfsd: minor 4.1 callback cleanup

已被回退的后续修复补丁c1ccfcf1a9bf NFSD: Reschedule CB operations when backchannel rpc_clnt is shut down

3 代码分析

struct nfs4_client中增加一个字段cl_cb_inflight表示未完成的回调的个数,注意这个补丁的标题是说竞争发生在两个函数之间。我们先来看一下补丁合入前的竞争,nfsd4_cb_release()__destroy_client()同时调用到radix_tree_node_free(),导致rcu_head在被释放之后又被释放了一次。

nfsd4_cb_release
  // nfsd4_run_cb // 这里补丁合入前后没有变化
  //   queue_work(callback_wq, &cb->cb_work)
  nfsd4_cb_recall_release // cb->cb_ops->release
    nfs4_put_stid
      idr_remove
        radix_tree_delete_item
          __radix_tree_delete
            delete_node
              radix_tree_node_free // __destroy_client中也调用到这里,并发

__destroy_client
  nfsd4_shutdown_callback
  free_client
    idr_destroy
      radix_tree_free_nodes
        radix_tree_node_free // nfsd4_cb_release也调用到这里,并发

合入补丁之后,nfsd4_cb_release()中的radix_tree_node_free()执行完后调用nfsd41_cb_inflight_end()唤醒nfsd4_shutdown_callback()free_client()才开始执行,避免了并发的场景。

nfsd4_cb_release
  // nfsd4_queue_cb // 这里补丁合入前后没有变化
  //   queue_work(callback_wq, &cb->cb_work)
  nfsd41_destroy_cb
    nfsd4_cb_recall_release // cb->cb_ops->release
      nfs4_put_stid
        idr_remove
          radix_tree_delete_item
            __radix_tree_delete
              delete_node
                radix_tree_node_free // 执行完后调用nfsd41_cb_inflight_end唤醒nfsd4_shutdown_callback
    nfsd41_cb_inflight_end
      atomic_dec_and_test
      wake_up_var // cl_cb_inflight变为0时,唤醒nfsd4_shutdown_callback

__destroy_client
  nfsd4_shutdown_callback
    nfsd41_cb_inflight_wait_complete // cl_cb_inflight变为0时,唤醒
  free_client
    idr_destroy
      radix_tree_free_nodes
        radix_tree_node_free // nfsd41_cb_inflight_wait_complete唤醒后才会执行到这里,没有并发的情况