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