Created
June 26, 2025 15:39
-
-
Save 27Cobalter/9d59b11171afd48de23ac410a4802d04 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #ifndef SHARED_MUTEX_H | |
| #define SHARED_MUTEX_H | |
| #include <pthread.h> | |
| #include <stdatomic.h> | |
| // 共有メモリに配置する構造体 | |
| typedef struct { | |
| pthread_mutex_t mutex; // プロセス間で共有される堅牢なミューテックス | |
| atomic_uint ref_count; // 参照カウンタ | |
| } shared_data_t; | |
| // 共有ミューテックスのハンドル | |
| typedef struct { | |
| int shm_fd; // 共有メモリのファイルディスクリプタ | |
| const char *name; // 共有メモリ名 | |
| shared_data_t *data; // 共有データへのポインタ | |
| } shared_mutex_t; | |
| /** | |
| * @brief 名前付き共有ミューテックスを作成またはオープンする。 | |
| * @param name ミューテックスの一意な名前 (例: "/my_app_mutex") | |
| * @return 成功した場合はshared_mutex_tのポインタ、失敗した場合はNULL。 | |
| */ | |
| shared_mutex_t* shared_mutex_create(const char *name); | |
| /** | |
| * @brief 共有ミューテックスをロックする。堅牢ミューテックスのため、戻り値の確認が必須。 | |
| * @param handle shared_mutex_createから返されたハンドル。 | |
| * @return 成功時は0、前の所有者がロックを保持したまま終了した場合はEOWNERDEAD、その他のエラーの場合は対応するエラーコード。 | |
| */ | |
| int shared_mutex_lock(shared_mutex_t *handle); | |
| /** | |
| * @brief 共有ミューテックスのロックを解放する。 | |
| * @param handle shared_mutex_createから返されたハンドル。 | |
| */ | |
| void shared_mutex_unlock(shared_mutex_t *handle); | |
| /** | |
| * @brief ミューテックスの状態を一貫性のあるものとしてマークする。EOWNERDEADを受け取った後に呼び出す。 | |
| * @param handle shared_mutex_createから返されたハンドル。 | |
| * @return 成功時は0、エラーの場合は対応するエラーコード。 | |
| */ | |
| int shared_mutex_make_consistent(shared_mutex_t *handle); | |
| /** | |
| * @brief 共有ミューテックスのハンドルを解放し、必要であれば共有リソースの後片付けを行う。 | |
| * @param handle shared_mutex_createから返されたハンドル。 | |
| */ | |
| void shared_mutex_destroy(shared_mutex_t *handle); | |
| #endif // SHARED_MUTEX_H | |
| #include "shared_mutex.h" | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <sys/mman.h> | |
| #include <sys/stat.h> | |
| #include <fcntl.h> | |
| #include <errno.h> | |
| #include <sys/file.h> | |
| #define LOCK_FILE_SUFFIX ".lock" | |
| // 補助ロックファイルを取得し、排他ロックをかけるヘルパー関数 | |
| static int acquire_init_lock(const char* base_name, char* lock_path_buf, size_t buf_size) { | |
| snprintf(lock_path_buf, buf_size, "/tmp%s%s", base_name, LOCK_FILE_SUFFIX); | |
| int lock_fd = open(lock_path_buf, O_CREAT | O_RDWR, 0666); | |
| if (lock_fd == -1) { | |
| perror("open lock file"); | |
| return -1; | |
| } | |
| if (flock(lock_fd, LOCK_EX) == -1) { | |
| perror("flock"); | |
| close(lock_fd); | |
| return -1; | |
| } | |
| return lock_fd; | |
| } | |
| // 補助ロックを解放するヘルパー関数 | |
| static void release_init_lock(int lock_fd) { | |
| flock(lock_fd, LOCK_UN); | |
| close(lock_fd); | |
| } | |
| shared_mutex_t* shared_mutex_create(const char *name) { | |
| char lock_path[256]; | |
| int lock_fd = -1; | |
| int shm_fd = -1; | |
| shared_data_t *data = MAP_FAILED; | |
| shared_mutex_t *handle = NULL; | |
| int is_new = 0; | |
| lock_fd = acquire_init_lock(name, lock_path, sizeof(lock_path)); | |
| if (lock_fd == -1) return NULL; | |
| shm_fd = shm_open(name, O_RDWR | O_CREAT, 0666); | |
| if (shm_fd == -1) { | |
| perror("shm_open"); | |
| goto error; | |
| } | |
| struct stat shm_stat; | |
| if (fstat(shm_fd, &shm_stat) == -1) { | |
| perror("fstat"); | |
| goto error; | |
| } | |
| if (shm_stat.st_size == 0) { | |
| is_new = 1; | |
| if (ftruncate(shm_fd, sizeof(shared_data_t)) == -1) { | |
| perror("ftruncate"); | |
| goto error; | |
| } | |
| } | |
| data = (shared_data_t*)mmap(NULL, sizeof(shared_data_t), | |
| PROT_READ | PROT_WRITE, | |
| MAP_SHARED, shm_fd, 0); | |
| if (data == MAP_FAILED) { | |
| perror("mmap"); | |
| goto error; | |
| } | |
| if (is_new) { | |
| pthread_mutexattr_t attr; | |
| pthread_mutexattr_init(&attr); | |
| pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); | |
| pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); | |
| pthread_mutex_init(&data->mutex, &attr); | |
| pthread_mutexattr_destroy(&attr); | |
| atomic_init(&data->ref_count, 0); | |
| } | |
| release_init_lock(lock_fd); | |
| lock_fd = -1; | |
| atomic_fetch_add(&data->ref_count, 1); | |
| handle = malloc(sizeof(shared_mutex_t)); | |
| handle->shm_fd = shm_fd; | |
| handle->name = name; | |
| handle->data = data; | |
| return handle; | |
| error: | |
| if (data != MAP_FAILED) munmap(data, sizeof(shared_data_t)); | |
| if (shm_fd != -1) close(shm_fd); | |
| if (lock_fd != -1) release_init_lock(lock_fd); | |
| if (is_new) shm_unlink(name); | |
| return NULL; | |
| } | |
| int shared_mutex_lock(shared_mutex_t *handle) { | |
| if (handle && handle->data) { | |
| return pthread_mutex_lock(&handle->data->mutex); | |
| } | |
| return EINVAL; | |
| } | |
| void shared_mutex_unlock(shared_mutex_t *handle) { | |
| if (handle && handle->data) { | |
| pthread_mutex_unlock(&handle->data->mutex); | |
| } | |
| } | |
| int shared_mutex_make_consistent(shared_mutex_t *handle) { | |
| if (handle && handle->data) { | |
| return pthread_mutex_consistent(&handle->data->mutex); | |
| } | |
| return EINVAL; | |
| } | |
| void shared_mutex_destroy(shared_mutex_t *handle) { | |
| if (!handle) return; | |
| if (atomic_fetch_sub(&handle->data->ref_count, 1) == 1) { | |
| char lock_path[256]; | |
| int lock_fd = acquire_init_lock(handle->name, lock_path, sizeof(lock_path)); | |
| if (lock_fd != -1) { | |
| if (atomic_load(&handle->data->ref_count) == 0) { | |
| printf("Process %d: This is the last process. Cleaning up all shared resources.\n", getpid()); | |
| pthread_mutex_destroy(&handle->data->mutex); | |
| shm_unlink(handle->name); | |
| unlink(lock_path); | |
| } | |
| release_init_lock(lock_fd); | |
| } else { | |
| fprintf(stderr, "Warning: Failed to acquire lock for cleanup. Resources may be leaked.\n"); | |
| } | |
| } | |
| munmap(handle->data, sizeof(shared_data_t)); | |
| close(handle->shm_fd); | |
| free(handle); | |
| } | |
| #include "shared_mutex.h" | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <sys/wait.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <signal.h> | |
| #define MUTEX_NAME "/my_robust_mutex_1234" | |
| // ロックを取得し、作業を行う関数 | |
| void perform_work(const char* process_name) { | |
| printf("[%s:%d] Trying to create/open shared mutex...\n", process_name, getpid()); | |
| shared_mutex_t *mutex = shared_mutex_create(MUTEX_NAME); | |
| if (!mutex) { | |
| fprintf(stderr, "[%s:%d] FATAL: Failed to create mutex.\n", process_name, getpid()); | |
| return; | |
| } | |
| printf("[%s:%d] Trying to lock...\n", process_name, getpid()); | |
| int ret = shared_mutex_lock(mutex); | |
| if (ret == EOWNERDEAD) { | |
| fprintf(stderr, "[%s:%d] WARNING: Previous owner died holding the lock! State may be inconsistent.\n", process_name, getpid()); | |
| printf("[%s:%d] Attempting to recover the mutex state...\n", process_name, getpid()); | |
| // ★★★ 本来はここで共有データの一貫性を修復する処理を行う ★★★ | |
| shared_mutex_make_consistent(mutex); | |
| printf("[%s:%d] Mutex state recovered. Lock is now held.\n", process_name, getpid()); | |
| } else if (ret != 0) { | |
| fprintf(stderr, "[%s:%d] FATAL: Failed to lock mutex: %s\n", process_name, getpid(), strerror(ret)); | |
| shared_mutex_destroy(mutex); | |
| return; | |
| } | |
| printf("[%s:%d] Lock acquired successfully. Working for 3 seconds...\n", process_name, getpid()); | |
| sleep(3); | |
| printf("[%s:%d] Work done. Unlocking.\n", process_name, getpid()); | |
| shared_mutex_unlock(mutex); | |
| printf("[%s:%d] Destroying handle.\n", process_name, getpid()); | |
| shared_mutex_destroy(mutex); | |
| } | |
| // ロックを取得した直後にクラッシュする関数 | |
| void perform_work_and_crash(const char* process_name) { | |
| printf("[%s:%d] Trying to create/open shared mutex...\n", process_name, getpid()); | |
| shared_mutex_t *mutex = shared_mutex_create(MUTEX_NAME); | |
| if (!mutex) { | |
| fprintf(stderr, "[%s:%d] FATAL: Failed to create mutex.\n", process_name, getpid()); | |
| return; | |
| } | |
| printf("[%s:%d] Trying to lock...\n", process_name, getpid()); | |
| int ret = shared_mutex_lock(mutex); | |
| if (ret != 0) { | |
| fprintf(stderr, "[%s:%d] FATAL: Could not acquire lock before crashing: %s\n", process_name, getpid(), strerror(ret)); | |
| shared_mutex_destroy(mutex); | |
| return; | |
| } | |
| printf("[%s:%d] Lock acquired. Crashing deliberately in 1 second...\n", process_name, getpid()); | |
| sleep(1); | |
| // SIGKILLで強制終了(クラッシュをシミュレート) | |
| raise(SIGKILL); | |
| } | |
| int main() { | |
| // 念のため、起動時に古いリソースをクリーンアップ | |
| shm_unlink(MUTEX_NAME); | |
| char lock_path[256]; | |
| snprintf(lock_path, sizeof(lock_path), "/tmp%s.lock", MUTEX_NAME); | |
| unlink(lock_path); | |
| printf("--- TEST 1: Normal concurrent access ---\n"); | |
| pid_t pid1 = fork(); | |
| if (pid1 == 0) { | |
| perform_work("Process A"); | |
| exit(0); | |
| } | |
| sleep(1); // 少しタイミングをずらす | |
| pid_t pid2 = fork(); | |
| if (pid2 == 0) { | |
| perform_work("Process B"); | |
| exit(0); | |
| } | |
| waitpid(pid1, NULL, 0); | |
| waitpid(pid2, NULL, 0); | |
| printf("\n--- TEST 1: Finished ---\n\n"); | |
| sleep(2); | |
| printf("--- TEST 2: Crash and Recovery Scenario ---\n"); | |
| pid_t pid_crasher = fork(); | |
| if (pid_crasher == 0) { | |
| // このプロセスはロックを取得後クラッシュする | |
| perform_work_and_crash("Crasher"); | |
| exit(0); // ここには到達しない | |
| } | |
| // クラッシュするプロセスがロックを取得するのを待つ | |
| sleep(3); | |
| pid_t pid_recoverer = fork(); | |
| if (pid_recoverer == 0) { | |
| // このプロセスがEOWNERDEADを検知し、回復を試みる | |
| perform_work("Recoverer"); | |
| exit(0); | |
| } | |
| waitpid(pid_crasher, NULL, 0); | |
| waitpid(pid_recoverer, NULL, 0); | |
| printf("\n--- TEST 2: Finished ---\n\n"); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment