글루시스 이헌제가 OpenZFS 운영 중 겪은 `zfs create` 자동 마운트 실패 버그를 분석하고 upstream PR #18611로 병합된 수정 과정을 정리한다. 원인은 조회 성격의 `zfs userspace`가 마운트 경로와 동일하게 dataset의 배타적 소유(`ds_owner`)를 요구하도록 잘못 구현돼 있어, 10초 주기 모니터링 데몬과 `zfs create`가 겹치면 `EBUSY`가 튀는 레이스였다. 해결은 조회 경로를 소유(`own`) 대신 참조(`hold`)로 바꾸는 한 줄이었지만, 그 과정에서 config lock 생명주기와 ABBA 교착까지 함께 손봐야 했다.
핵심 포인트- `zfs create` 자동 마운트가 `mountpoint or dataset is busy` (EBUSY)로 간헐 실패, 재현 조건은 모니터링 데몬의 주기적 `zfs userspace/groupspace/projectspace` 호출과 동시성.
- 원인은 세 잠금 중 `ds_owner`(mutex 보호, 배타적) 층에서의 선점 경쟁. 먼저 도착한 쪽이 owner를 잡으면 나머지는 EBUSY.
- 진짜 문제는 잠금 자체가 아니라 설계 불일치. 쿼터 숫자를 읽기만 하는 조회 연산에 배타적 소유가 필요 없었다.
- 수정은 `zfs userspace` 경로에서 `dmu_objset_own()`을 `dmu_objset_hold()`로 교체. config lock 순서 역전과 경로 분기 관리가 부수적으로 필요.
상세 정리- 문제 재현: `zfs create ci_zfsPool_1B/ci_zfsVol_beta` 후 자동 마운트가 실패하며 `filesystem successfully created, but not mounted` 로그. 스토리지 모니터링 데몬이 10초 주기로 쿼터 조회를 돌리는 환경에서만 재현.
- 관여 잠금 세 개를 분리 분석: `z_teardown_lock`(zfsvfs 생명주기·inode 캐시 보호, R/W), `dp_config_rwlock`(풀의 dataset 트리·속성 보호, R/W), `ds_owner`(dataset 배타 소유 표시, mutex).
- EBUSY 트리거 시퀀스: 조회 쪽이 임시 인스턴스를 만들려고 `dmu_objset_own(FTAG)` 호출 → `ds_owner=FTAG` 세팅 → 뒤이어 `zfs create`가 같은 dataset을 `own()` 시도하다 이미 소유자가 있어 EBUSY.
- 근본 판단: `zfs userspace`는 값만 읽는 관측 연산이라 배타적 owner가 될 필요가 없다. `zfs create` 쪽만 `z_os` objset 연결 등 구조 변경이 있어 owner가 필수.
- 해결책 함수 `zfsvfs_create_hold()` 신설. 마운트되지 않은 dataset을 대상으로 `dmu_objset_hold()`로 참조만 잡고, `dmu_objset_type()` 체크가 없는 hold의 특성상 `DMU_OST_ZFS`인지 명시적으로 확인한 뒤 `zfsvfs_create_impl()`로 임시 인스턴스 구성.
- `zfsvfs_hold()`도 갈래를 분기. `getzfsvfs()`로 이미 마운트된 인스턴스가 없으면 신설 함수로 진입하고, teardown lock 진입 전에 config lock을 먼저 해제해 ABBA 교착을 예방.
- ABBA 원인: `own()`은 config lock을 획득했다 반환 전에 놓지만 `hold()`는 유지한 채 반환한다. 그대로 두면 hold 경로가 `config(R) → teardown` 순서로 잠가 `zfs_resume_fs()`의 `teardown(W) → config` 순서와 교차한다.
- 경로 구분 실패 사례: 처음엔 `ds_owner != NULL`로 hold/own을 구분하려 했으나 이 필드가 mutex 보호라 검사 자체가 새 레이스가 됨. 대신 `zfsvfs_t`에 `z_use_hold` boolean을 두고 `zfsvfs_create_hold()`가 딱 한 번 세팅.
- `zfsvfs_rele()`도 대칭 처리. `z_use_hold`면 config lock을 재획득 후 `dmu_objset_rele()`, 아니면 기존대로 `dmu_objset_disown(..., B_TRUE, ...)`.
- 검증은 userspace/groupspace/projectspace 각각에 대해 `zfs mount &` 백그라운드와 조회를 50회 부딪히는 스트레스 케이스를 회귀 테스트로 추가. 수정 후 EBUSY 재현이 사라짐을 확인.
- 결과 PR은 OpenZFS upstream #18611로 병합. 코드 변경 자체는 조회 경로 하나지만, 잠금 순서와 경로 플래그 관리까지 파생돼 최종 diff가 커진 사례.
왜 읽나파일시스템/스토리지 저수준을 만지는 커널·시스템 엔지니어에게 "원인은 rwlock이 아니라 API 선택"이라는 진단 관점과, hold/own·config lock·teardown lock의 ABBA를 실제 코드로 해결한 참조 자료.