Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
official-stockfish
GitHub Repository: official-stockfish/Stockfish
Path: blob/master/src/shm_linux.h
473 views
1
/*
2
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
3
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
4
5
Stockfish is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation, either version 3 of the License, or
8
(at your option) any later version.
9
10
Stockfish is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public License for more details.
14
15
You should have received a copy of the GNU General Public License
16
along with this program. If not, see <http://www.gnu.org/licenses/>.
17
*/
18
19
#ifndef SHM_LINUX_H_INCLUDED
20
#define SHM_LINUX_H_INCLUDED
21
22
#include <atomic>
23
#include <cassert>
24
#include <cerrno>
25
#include <cstdlib>
26
#include <cstring>
27
#include <cstdio>
28
#include <dirent.h>
29
#include <mutex>
30
#include <new>
31
#include <optional>
32
#include <pthread.h>
33
#include <string>
34
#include <inttypes.h>
35
#include <type_traits>
36
#include <unordered_set>
37
38
#include <fcntl.h>
39
#include <signal.h>
40
#include <sys/file.h>
41
#include <sys/mman.h>
42
#include <sys/stat.h>
43
#include <unistd.h>
44
45
#if defined(__NetBSD__) || defined(__DragonFly__) || defined(__linux__)
46
#include <limits.h>
47
#define SF_MAX_SEM_NAME_LEN NAME_MAX
48
#elif defined(__APPLE__)
49
#define SF_MAX_SEM_NAME_LEN 31
50
#else
51
#define SF_MAX_SEM_NAME_LEN 255
52
#endif
53
54
55
namespace Stockfish::shm {
56
57
namespace detail {
58
59
struct ShmHeader {
60
static constexpr uint32_t SHM_MAGIC = 0xAD5F1A12;
61
pthread_mutex_t mutex;
62
std::atomic<uint32_t> ref_count{0};
63
std::atomic<bool> initialized{false};
64
uint32_t magic = SHM_MAGIC;
65
};
66
67
class SharedMemoryBase {
68
public:
69
virtual ~SharedMemoryBase() = default;
70
virtual void close() noexcept = 0;
71
virtual const std::string& name() const noexcept = 0;
72
};
73
74
class SharedMemoryRegistry {
75
private:
76
static std::mutex registry_mutex_;
77
static std::unordered_set<SharedMemoryBase*> active_instances_;
78
79
public:
80
static void register_instance(SharedMemoryBase* instance) {
81
std::scoped_lock lock(registry_mutex_);
82
active_instances_.insert(instance);
83
}
84
85
static void unregister_instance(SharedMemoryBase* instance) {
86
std::scoped_lock lock(registry_mutex_);
87
active_instances_.erase(instance);
88
}
89
90
static void cleanup_all() noexcept {
91
std::scoped_lock lock(registry_mutex_);
92
for (auto* instance : active_instances_)
93
instance->close();
94
active_instances_.clear();
95
}
96
};
97
98
inline std::mutex SharedMemoryRegistry::registry_mutex_;
99
inline std::unordered_set<SharedMemoryBase*> SharedMemoryRegistry::active_instances_;
100
101
class CleanupHooks {
102
private:
103
static std::once_flag register_once_;
104
105
static void handle_signal(int sig) noexcept {
106
SharedMemoryRegistry::cleanup_all();
107
_Exit(128 + sig);
108
}
109
110
static void register_signal_handlers() noexcept {
111
std::atexit([]() { SharedMemoryRegistry::cleanup_all(); });
112
113
constexpr int signals[] = {SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE,
114
SIGSEGV, SIGTERM, SIGBUS, SIGSYS, SIGXCPU, SIGXFSZ};
115
116
struct sigaction sa;
117
sa.sa_handler = handle_signal;
118
sigemptyset(&sa.sa_mask);
119
sa.sa_flags = 0;
120
121
for (int sig : signals)
122
sigaction(sig, &sa, nullptr);
123
}
124
125
public:
126
static void ensure_registered() noexcept {
127
std::call_once(register_once_, register_signal_handlers);
128
}
129
};
130
131
inline std::once_flag CleanupHooks::register_once_;
132
133
134
inline int portable_fallocate(int fd, off_t offset, off_t length) {
135
#ifdef __APPLE__
136
fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, offset, length, 0};
137
int ret = fcntl(fd, F_PREALLOCATE, &store);
138
if (ret == -1)
139
{
140
store.fst_flags = F_ALLOCATEALL;
141
ret = fcntl(fd, F_PREALLOCATE, &store);
142
}
143
if (ret != -1)
144
ret = ftruncate(fd, offset + length);
145
return ret;
146
#else
147
return posix_fallocate(fd, offset, length);
148
#endif
149
}
150
151
} // namespace detail
152
153
template<typename T>
154
class SharedMemory: public detail::SharedMemoryBase {
155
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
156
static_assert(!std::is_pointer_v<T>, "T cannot be a pointer type");
157
158
private:
159
std::string name_;
160
int fd_ = -1;
161
void* mapped_ptr_ = nullptr;
162
T* data_ptr_ = nullptr;
163
detail::ShmHeader* header_ptr_ = nullptr;
164
size_t total_size_ = 0;
165
std::string sentinel_base_;
166
std::string sentinel_path_;
167
168
static constexpr size_t calculate_total_size() noexcept {
169
return sizeof(T) + sizeof(detail::ShmHeader);
170
}
171
172
static std::string make_sentinel_base(const std::string& name) {
173
uint64_t hash = std::hash<std::string>{}(name);
174
char buf[32];
175
std::snprintf(buf, sizeof(buf), "sfshm_%016" PRIx64, static_cast<uint64_t>(hash));
176
return buf;
177
}
178
179
public:
180
explicit SharedMemory(const std::string& name) noexcept :
181
name_(name),
182
total_size_(calculate_total_size()),
183
sentinel_base_(make_sentinel_base(name)) {}
184
185
~SharedMemory() noexcept override {
186
detail::SharedMemoryRegistry::unregister_instance(this);
187
close();
188
}
189
190
SharedMemory(const SharedMemory&) = delete;
191
SharedMemory& operator=(const SharedMemory&) = delete;
192
193
SharedMemory(SharedMemory&& other) noexcept :
194
name_(std::move(other.name_)),
195
fd_(other.fd_),
196
mapped_ptr_(other.mapped_ptr_),
197
data_ptr_(other.data_ptr_),
198
header_ptr_(other.header_ptr_),
199
total_size_(other.total_size_),
200
sentinel_base_(std::move(other.sentinel_base_)),
201
sentinel_path_(std::move(other.sentinel_path_)) {
202
203
detail::SharedMemoryRegistry::unregister_instance(&other);
204
detail::SharedMemoryRegistry::register_instance(this);
205
other.reset();
206
}
207
208
SharedMemory& operator=(SharedMemory&& other) noexcept {
209
if (this != &other)
210
{
211
detail::SharedMemoryRegistry::unregister_instance(this);
212
close();
213
214
name_ = std::move(other.name_);
215
fd_ = other.fd_;
216
mapped_ptr_ = other.mapped_ptr_;
217
data_ptr_ = other.data_ptr_;
218
header_ptr_ = other.header_ptr_;
219
total_size_ = other.total_size_;
220
sentinel_base_ = std::move(other.sentinel_base_);
221
sentinel_path_ = std::move(other.sentinel_path_);
222
223
detail::SharedMemoryRegistry::unregister_instance(&other);
224
detail::SharedMemoryRegistry::register_instance(this);
225
226
other.reset();
227
}
228
return *this;
229
}
230
231
[[nodiscard]] bool open(const T& initial_value) noexcept {
232
detail::CleanupHooks::ensure_registered();
233
234
bool retried_stale = false;
235
236
while (true)
237
{
238
if (is_open())
239
return false;
240
241
bool created_new = false;
242
fd_ = shm_open(name_.c_str(), O_CREAT | O_EXCL | O_RDWR, 0666);
243
244
if (fd_ == -1)
245
{
246
fd_ = shm_open(name_.c_str(), O_RDWR, 0666);
247
if (fd_ == -1)
248
return false;
249
}
250
else
251
created_new = true;
252
253
if (!lock_file(LOCK_EX))
254
{
255
::close(fd_);
256
reset();
257
return false;
258
}
259
260
bool invalid_header = false;
261
bool success =
262
created_new ? setup_new_region(initial_value) : setup_existing_region(invalid_header);
263
264
if (!success)
265
{
266
if (created_new || invalid_header)
267
shm_unlink(name_.c_str());
268
if (mapped_ptr_)
269
unmap_region();
270
unlock_file();
271
::close(fd_);
272
reset();
273
274
if (!created_new && invalid_header && !retried_stale)
275
{
276
retried_stale = true;
277
continue;
278
}
279
return false;
280
}
281
282
if (!lock_shared_mutex())
283
{
284
if (created_new)
285
shm_unlink(name_.c_str());
286
if (mapped_ptr_)
287
unmap_region();
288
unlock_file();
289
::close(fd_);
290
reset();
291
292
if (!created_new && !retried_stale)
293
{
294
retried_stale = true;
295
continue;
296
}
297
return false;
298
}
299
300
if (!create_sentinel_file_locked())
301
{
302
unlock_shared_mutex();
303
unmap_region();
304
if (created_new)
305
shm_unlink(name_.c_str());
306
unlock_file();
307
::close(fd_);
308
reset();
309
return false;
310
}
311
312
header_ptr_->ref_count.fetch_add(1, std::memory_order_acq_rel);
313
314
unlock_shared_mutex();
315
unlock_file();
316
detail::SharedMemoryRegistry::register_instance(this);
317
return true;
318
}
319
}
320
321
void close() noexcept override {
322
if (fd_ == -1 && mapped_ptr_ == nullptr)
323
return;
324
325
bool remove_region = false;
326
bool file_locked = lock_file(LOCK_EX);
327
bool mutex_locked = false;
328
329
if (file_locked && header_ptr_ != nullptr)
330
mutex_locked = lock_shared_mutex();
331
332
if (mutex_locked)
333
{
334
if (header_ptr_)
335
{
336
header_ptr_->ref_count.fetch_sub(1, std::memory_order_acq_rel);
337
}
338
remove_sentinel_file();
339
remove_region = !has_other_live_sentinels_locked();
340
unlock_shared_mutex();
341
}
342
else
343
{
344
remove_sentinel_file();
345
decrement_refcount_relaxed();
346
}
347
348
unmap_region();
349
350
if (remove_region)
351
shm_unlink(name_.c_str());
352
353
if (file_locked)
354
unlock_file();
355
356
if (fd_ != -1)
357
{
358
::close(fd_);
359
fd_ = -1;
360
}
361
362
reset();
363
}
364
365
const std::string& name() const noexcept override { return name_; }
366
367
[[nodiscard]] bool is_open() const noexcept { return fd_ != -1 && mapped_ptr_ && data_ptr_; }
368
369
[[nodiscard]] const T& get() const noexcept { return *data_ptr_; }
370
371
[[nodiscard]] const T* operator->() const noexcept { return data_ptr_; }
372
373
[[nodiscard]] const T& operator*() const noexcept { return *data_ptr_; }
374
375
[[nodiscard]] uint32_t ref_count() const noexcept {
376
return header_ptr_ ? header_ptr_->ref_count.load(std::memory_order_acquire) : 0;
377
}
378
379
[[nodiscard]] bool is_initialized() const noexcept {
380
return header_ptr_ ? header_ptr_->initialized.load(std::memory_order_acquire) : false;
381
}
382
383
static void cleanup_all_instances() noexcept { detail::SharedMemoryRegistry::cleanup_all(); }
384
385
private:
386
void reset() noexcept {
387
fd_ = -1;
388
mapped_ptr_ = nullptr;
389
data_ptr_ = nullptr;
390
header_ptr_ = nullptr;
391
sentinel_path_.clear();
392
}
393
394
void unmap_region() noexcept {
395
if (mapped_ptr_)
396
{
397
munmap(mapped_ptr_, total_size_);
398
mapped_ptr_ = nullptr;
399
data_ptr_ = nullptr;
400
header_ptr_ = nullptr;
401
}
402
}
403
404
[[nodiscard]] bool lock_file(int operation) noexcept {
405
if (fd_ == -1)
406
return false;
407
408
while (flock(fd_, operation) == -1)
409
{
410
if (errno == EINTR)
411
continue;
412
return false;
413
}
414
return true;
415
}
416
417
void unlock_file() noexcept {
418
if (fd_ == -1)
419
return;
420
421
while (flock(fd_, LOCK_UN) == -1)
422
{
423
if (errno == EINTR)
424
continue;
425
break;
426
}
427
}
428
429
std::string sentinel_full_path(pid_t pid) const {
430
std::string path = "/dev/shm/";
431
path += sentinel_base_;
432
path.push_back('.');
433
path += std::to_string(pid);
434
return path;
435
}
436
437
void decrement_refcount_relaxed() noexcept {
438
if (!header_ptr_)
439
return;
440
441
uint32_t expected = header_ptr_->ref_count.load(std::memory_order_relaxed);
442
while (expected != 0
443
&& !header_ptr_->ref_count.compare_exchange_weak(
444
expected, expected - 1, std::memory_order_acq_rel, std::memory_order_relaxed))
445
{}
446
}
447
448
bool create_sentinel_file_locked() noexcept {
449
if (!header_ptr_)
450
return false;
451
452
const pid_t self_pid = getpid();
453
sentinel_path_ = sentinel_full_path(self_pid);
454
455
for (int attempt = 0; attempt < 2; ++attempt)
456
{
457
int fd = ::open(sentinel_path_.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
458
if (fd != -1)
459
{
460
::close(fd);
461
return true;
462
}
463
464
if (errno == EEXIST)
465
{
466
::unlink(sentinel_path_.c_str());
467
decrement_refcount_relaxed();
468
continue;
469
}
470
471
break;
472
}
473
474
sentinel_path_.clear();
475
return false;
476
}
477
478
void remove_sentinel_file() noexcept {
479
if (!sentinel_path_.empty())
480
{
481
::unlink(sentinel_path_.c_str());
482
sentinel_path_.clear();
483
}
484
}
485
486
static bool pid_is_alive(pid_t pid) noexcept {
487
if (pid <= 0)
488
return false;
489
490
if (kill(pid, 0) == 0)
491
return true;
492
493
return errno == EPERM;
494
}
495
496
[[nodiscard]] bool initialize_shared_mutex() noexcept {
497
if (!header_ptr_)
498
return false;
499
500
pthread_mutexattr_t attr;
501
if (pthread_mutexattr_init(&attr) != 0)
502
return false;
503
504
bool success = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) == 0;
505
#ifdef PTHREAD_MUTEX_ROBUST
506
if (success)
507
success = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST) == 0;
508
#endif
509
510
if (success)
511
success = pthread_mutex_init(&header_ptr_->mutex, &attr) == 0;
512
513
pthread_mutexattr_destroy(&attr);
514
return success;
515
}
516
517
[[nodiscard]] bool lock_shared_mutex() noexcept {
518
if (!header_ptr_)
519
return false;
520
521
while (true)
522
{
523
int rc = pthread_mutex_lock(&header_ptr_->mutex);
524
if (rc == 0)
525
return true;
526
527
#ifdef PTHREAD_MUTEX_ROBUST
528
if (rc == EOWNERDEAD)
529
{
530
if (pthread_mutex_consistent(&header_ptr_->mutex) == 0)
531
return true;
532
return false;
533
}
534
#endif
535
536
if (rc == EINTR)
537
continue;
538
539
return false;
540
}
541
}
542
543
void unlock_shared_mutex() noexcept {
544
if (header_ptr_)
545
pthread_mutex_unlock(&header_ptr_->mutex);
546
}
547
548
bool has_other_live_sentinels_locked() const noexcept {
549
DIR* dir = opendir("/dev/shm");
550
if (!dir)
551
return false;
552
553
std::string prefix = sentinel_base_ + ".";
554
bool found = false;
555
556
while (dirent* entry = readdir(dir))
557
{
558
std::string name = entry->d_name;
559
if (name.rfind(prefix, 0) != 0)
560
continue;
561
562
auto pid_str = name.substr(prefix.size());
563
char* end = nullptr;
564
long value = std::strtol(pid_str.c_str(), &end, 10);
565
if (!end || *end != '\0')
566
continue;
567
568
pid_t pid = static_cast<pid_t>(value);
569
if (pid_is_alive(pid))
570
{
571
found = true;
572
break;
573
}
574
575
std::string stale_path = std::string("/dev/shm/") + name;
576
::unlink(stale_path.c_str());
577
const_cast<SharedMemory*>(this)->decrement_refcount_relaxed();
578
}
579
580
closedir(dir);
581
return found;
582
}
583
584
[[nodiscard]] bool setup_new_region(const T& initial_value) noexcept {
585
if (ftruncate(fd_, static_cast<off_t>(total_size_)) == -1)
586
return false;
587
588
if (detail::portable_fallocate(fd_, 0, static_cast<off_t>(total_size_)) != 0)
589
return false;
590
591
mapped_ptr_ = mmap(nullptr, total_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);
592
if (mapped_ptr_ == MAP_FAILED)
593
{
594
mapped_ptr_ = nullptr;
595
return false;
596
}
597
598
data_ptr_ = static_cast<T*>(mapped_ptr_);
599
header_ptr_ =
600
reinterpret_cast<detail::ShmHeader*>(static_cast<char*>(mapped_ptr_) + sizeof(T));
601
602
new (header_ptr_) detail::ShmHeader{};
603
new (data_ptr_) T{initial_value};
604
605
if (!initialize_shared_mutex())
606
return false;
607
608
header_ptr_->ref_count.store(0, std::memory_order_release);
609
header_ptr_->initialized.store(true, std::memory_order_release);
610
return true;
611
}
612
613
[[nodiscard]] bool setup_existing_region(bool& invalid_header) noexcept {
614
invalid_header = false;
615
616
struct stat st;
617
fstat(fd_, &st);
618
if (static_cast<size_t>(st.st_size) < total_size_)
619
{
620
invalid_header = true;
621
return false;
622
}
623
624
mapped_ptr_ = mmap(nullptr, total_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);
625
if (mapped_ptr_ == MAP_FAILED)
626
{
627
mapped_ptr_ = nullptr;
628
return false;
629
}
630
631
data_ptr_ = static_cast<T*>(mapped_ptr_);
632
header_ptr_ = std::launder(
633
reinterpret_cast<detail::ShmHeader*>(static_cast<char*>(mapped_ptr_) + sizeof(T)));
634
635
if (!header_ptr_->initialized.load(std::memory_order_acquire)
636
|| header_ptr_->magic != detail::ShmHeader::SHM_MAGIC)
637
{
638
invalid_header = true;
639
unmap_region();
640
return false;
641
}
642
643
return true;
644
}
645
};
646
647
template<typename T>
648
[[nodiscard]] std::optional<SharedMemory<T>> create_shared(const std::string& name,
649
const T& initial_value) noexcept {
650
SharedMemory<T> shm(name);
651
if (shm.open(initial_value))
652
return shm;
653
return std::nullopt;
654
}
655
656
} // namespace Stockfish::shm
657
658
#endif // #ifndef SHM_LINUX_H_INCLUDED
659
660