# ondemand 并发时序 ``` task daemon =========== ========== send_req close_devfd close_anonfd daemon_read (for OPEN) copen (write on devfd) (for READ) CACHEFILES_IOC_READ_COMPLETE ioctl (on anonfd) ``` ## 1. task:send_req 与 daemon:close_devfd 访问 erofs 文件的进程在触发 ondemand 的时候,通过 cachefiles_ondemand_send_req() 将请求添加到 xarray 中,即 enqueue request ``` cachefiles_ondemand_init_object cachefiles_ondemand_clean_object cachefiles_ondemand_read cachefiles_ondemand_send_req ``` daemon 在 close devfd 的时候需要将 xarray 中的请求 flush 掉,即 flush request ``` cachefiles_daemon_release cachefiles_flush_reqs ``` enqueue request 与 flush request 这两步操作通过 cache->flags 的 CACHEFILES_DEAD bit 进行同步,即 ``` # flush request cachefiles_daemon_release set CACHEFILES_DEAD bit cachefiles_flush_reqs # flush request ``` ``` # enqueue request cachefiles_ondemand_send_req # if CACHEFILES_DEAD bit not set: # enqueue request ``` 但是这里有两个时序需要注意 1) enqueue 的时候 a) test CACHEFILES_DEAD bit, b) enqueue request 这两个操作作为一个整体必须是 atomic 的,否则会出现以下 race ``` /* * Stop enqueuing the request when daemon is dying. The * following two operations need to be atomic as a whole. * 1) check cache state, and * 2) enqueue request if cache is alive. * Otherwise the request may be enqueued after xarray has been * flushed, leaving the orphan request never being completed. * * CPU 1 CPU 2 * ===== ===== * test CACHEFILES_DEAD bit * set CACHEFILES_DEAD bit * flush requests in the xarray * enqueue the request */ ``` 因而使用了 spinlock (xarray->xa_lock) 锁来确保上述 atomic 的要求 ``` # enqueue request cachefiles_ondemand_send_req xa_lock # if CACHEFILES_DEAD bit not set: # enqueue request xa_unlock ``` ``` # flush request cachefiles_daemon_release set CACHEFILES_DEAD bit cachefiles_flush_reqs xa_lock # flush request xa_unlock ``` 2) 注意上述 flush request 的时候,没有把 "set CACHEFILES_DEAD bit" 也放在 lock 的 critical area 内 (主要是不想修改 cachefiles_daemon_release() 函数),这样 a) set CACHEFILES_DEAD bit, b) flush request 两个操作中间就有可能插入 enqueue request 路径的操作;如果 a) set CACHEFILES_DEAD bit, b) flush request 两个操作再发生乱序,就有可能导致以下时序 ``` /* * Make sure the following two operations won't be reordered. * 1) set CACHEFILES_DEAD bit * 2) flush requests in the xarray * Otherwise the request may be enqueued after xarray has been * flushed, leaving the orphan request never being completed. * * CPU 1 CPU 2 * ===== ===== * flush requests in the xarray * test CACHEFILES_DEAD bit * enqueue the request * set CACHEFILES_DEAD bit */ ``` 所以为了让 flush request 路径的 a) set CACHEFILES_DEAD bit, b) flush request 两个操作,不要发生乱序,在这两个操作的中间加了一个 memory barrier ``` # flush request cachefiles_daemon_release set CACHEFILES_DEAD bit cachefiles_flush_reqs smp_mb(); xa_lock # flush request xa_unlock ``` > 这里 flush request 路径中的 xa_lock 能不能充当 memory barrier 呢? > spinlock 的 lock 操作隐含的是 read acquire 语义,而 read acquire 语义则是,确保 read acquire 之后的内存访问指令都在 read acquire 之后执行,相当于具有抑制 LoadLoad/LoadStore reordering 的作用 > > ``` > read acquire > --------------------- > all memory operations > stay below the line > ``` > > 但是我们这里是需要抑制 Store\[Load|Store],所以 xa_lock 隐含的 read acquire 语义并不能解决这个问题 ## 2. task:send_req 与 daemon:close_anonfd 类似地,当 daemon 在 close anonfd 的时候需要将 xarray 中,与该 anonfd 相关的请求 flush 掉,主要是 READ/CLOSE 请求,即 flush request 在引入 failover 特性之后,close anonfd 的时候不再需要 flush READ 请求,但是仍然需要 flush CLOSE 请求;flush CLOSE 请求的原因请参考 [flush CLOSE requests when anon fd is closed](https://hackmd.io/YNsTQqLcQYOZ4gAlFWrNcA#flush-CLOSE-requests-when-anon-fd-is-closed) 这里 enqueue request 与 flush request 这两步操作通过 object->ondemand_id 进行同步,即 ``` # flush request cachefiles_ondemand_fd_release object->ondemand_id = CACHEFILES_ONDEMAND_ID_CLOSED # flush request ``` ``` # enqueue request cachefiles_ondemand_send_req # if object->ondemand_id valid (ondemand_id > 0): # enqueue request ``` 类似地,enqueue request 路径中 a) test ondemand_id, b) enqueue request 这两个操作作为一个整体必须是 atomic 的,因而这里也是使用了 spinlock (xarray->xa_lock) 锁来确保上述 atomic 的要求 ``` # flush request cachefiles_ondemand_fd_release xa_lock object->ondemand_id = CACHEFILES_ONDEMAND_ID_CLOSED # flush request xa_unlock ``` ``` # enqueue request cachefiles_ondemand_send_req xa_lock # if object->ondemand_id valid (ondemand_id > 0): # enqueue request xa_unlock ``` 后面在引入 failover 特性,支持 object state 之后,也就变成了 ``` # flush request cachefiles_ondemand_fd_release xa_lock set_object_close # flush request xa_unlock ``` ``` # enqueue request cachefiles_ondemand_send_req xa_lock # if object is not in close state: # enqueue request xa_unlock ``` ## 3. daemon: daemon_read 与 daemon: close_anonfd cachefiles_ondemand_daemon_read() 中,存在 a) search xarray, b) erase xarray 两个操作 ``` # for other request types xa_lock search the xarray to find a valid request clear CACHEFILES_REQ_NEW mark xa_unlock id = xas.xa_index; copy this request to user buffer on error path: xa_erase(..., id) ``` 对于 CLOSE 请求,在 read 得到一个 CLOSE 请求之后,就会执行 erase xarray 操作 ``` # for CLOSE requests xa_lock search the xarray to find a valid request clear CACHEFILES_REQ_NEW mark xa_unlock id = xas.xa_index; copy this request to user buffer xa_erase(..., id) ``` 可以看到上述 daemon_read 路径中存在 a) search xarray, b) erase xarray 两个操作,而这两个操作作为一个整体并不是 atomic 的 同时我们之前介绍过,daemon 在 close anonfd 的时候,会 flush request,此时就有可能导致以下时序 ``` P1 P2 ------------ ----------- xa_lock search the xarray to find a valid request clear CACHEFILES_REQ_NEW mark xa_unlock id = xas.xa_index; copy this request to user buffer close anon fd xa_lock flush related requets xa_unlock another request may be enqueued into the xarray, reusing the previous id xa_erase(..., id) # oops ``` 如果要用一个 spinlock 锁把上述 daemon_read 路径中的 a) search xarray, b) erase xarray 两个操作包起来,一个是实现起来比较麻烦,在 daemon: close_anonfd 路径中也要把相关的代码段用这个 spinlock 包起来;另外一个,daemon_read 路径中 "copy this request to user buffer" 这一步还可能有其他操作,例如对于 OPEN 请求会调用 cachefiles_ondemand_get_fd(),这些操作可能会陷入阻塞,不能在持有 spinlock 的语境下调用 因而现在的修复方法是,daemon: close_anonfd 路径中只对 CACHEFILES_REQ_NEW 标记的请求做 flush 操作 ``` P1 P2 ------------ ----------- xa_lock search the xarray to find a valid request clear CACHEFILES_REQ_NEW mark xa_unlock id = xas.xa_index; copy this request to user buffer close anon fd xa_lock flush related requets with CACHEFILES_REQ_NEW marked xa_unlock the request processed by P1 is not flushed xa_erase(..., id) ``` 请参考 [race between reading/flush requests](https://hackmd.io/YNsTQqLcQYOZ4gAlFWrNcA?view#race-between-readingflush-requests) ## 4. daemon: daemon_read 与 daemon: close_devfd 类似地,daemon 在 close devfd 的时候同样会 flush 所有请求,那么上述介绍的 race 有没有可能在 daemon: close_devfd 的时候触发呢? 答案是不会,因为 daemon: daemon_read 与 daemon: close_devfd 这两个操作根本不会并行发生,daemon: daemon_read 是对 devfd 进行 read 操作的时候触发的,那么既然 devfd 还在执行 read 操作,那么 devfd 根本就还不会被 close 掉