---
# System prepended metadata

title: ondemand 并发时序

---

# 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 掉