-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlock_manager_printer.py
751 lines (588 loc) · 34.6 KB
/
lock_manager_printer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
###
# Copyright 2022-present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###
"""Pretty-printers for mongo::LockManager resource locks.
After the pretty printers have been registered in GDB, the LockManager locks in a mongod process or
core file can be displayed with the following commands:
.. code-block:: python
(gdb) python lock_mgr = gdbmongo.LockManagerPrinter.from_global_service_context()
(gdb) python print(lock_mgr.val)
"""
import abc
import struct
import typing
import gdb
from gdbmongo import stdlib_printers, stdlib_xmethods
from gdbmongo.abseil_printers import (AbslFlatHashMapPrinter, AbslNodeHashMapPrinter,
AbslFlatHashSetPrinter, AbslNodeHashSetPrinter)
from gdbmongo.decorable_printer import DecorationIterator
from gdbmongo.gdbutil import gdb_is_libthread_db_loaded, gdb_lookup_value
from gdbmongo.libstdcxxutil import shared_ptr_get, unique_ptr_get
from gdbmongo.printer_protocol import (PrettyPrinterProtocol, SupportsChildren, SupportsDisplayHint,
SupportsToString)
from gdbmongo.static_immortal_printer import StaticImmortalPrinter
from gdbmongo.string_data_printer import StdStringPrinter
class ServiceContextDecorationMixin(typing.Protocol):
"""Class to add support for constructing from the global ServiceContext if the subclass already
supports constructing from a ServiceContext explicitly.
"""
Decoration = typing.TypeVar("Decoration", bound="ServiceContextDecorationMixin")
@classmethod
@abc.abstractmethod
def from_service_context(cls: typing.Type[Decoration], service_context: gdb.Value,
/) -> Decoration:
"""Return a Decoration from its decoration on ServiceContext."""
raise NotImplementedError
@classmethod
def from_global_service_context(cls: typing.Type[Decoration]) -> Decoration:
"""Return a Decoration printer from its decoration on the global ServiceContext."""
service_context = gdb_lookup_value("mongo::(anonymous namespace)::globalServiceContext")
assert service_context is not None
return cls.from_service_context(service_context)
# pylint: disable-next=missing-class-docstring
# pylint: disable-next=too-few-public-methods
class ResourceCatalogGetter(typing.Protocol):
@abc.abstractmethod
def lookup_resource_name(self, res_id: gdb.Value, /) -> typing.Optional[str]:
"""Return the database or collection namespace string of the resource."""
raise NotImplementedError
# pylint: disable-next=missing-class-docstring
# pylint: disable-next=too-few-public-methods
class CollectionCatalogGetter(typing.Protocol):
short_name: typing.ClassVar[str]
catalog_type: gdb.Type
@abc.abstractmethod
def __call__(self, decoration: gdb.Value, /) -> gdb.Value:
raise NotImplementedError
# We don't have to_string() or children() defined on _CollectionCatalogPrinter right now. Until we
# have a sense of how else we might want to use the CollectionCatalog in GDB pretty printers, it is
# probably best to mark the type as being internal to this module.
class _CollectionCatalogPrinter(ServiceContextDecorationMixin, ResourceCatalogGetter):
"""Pretty-printer for mongo::CollectionCatalog."""
def __init__(self, val: gdb.Value, /) -> None:
self.resources = val["_resourceInformation"]
self.val = val
def lookup_resource_name(self, res_id: gdb.Value, /) -> typing.Optional[str]:
"""Return the database or collection namespace string of the resource."""
iterator = stdlib_printers.StdMapPrinter("std::map", self.resources).children()
for ((_, iter_res_id), (_, iter_nss_set)) in zip(iterator, iterator):
if iter_res_id == res_id:
nss_set = iter_nss_set
break
else:
return None
namespaces = [
nss for (_, nss) in stdlib_printers.StdSetPrinter("std::set", nss_set).children()
]
return StdStringPrinter(namespaces[0]).string() if len(namespaces) == 1 else None
# pylint: disable-next=too-few-public-methods
class _LatestCollectionCatalogDecoration(CollectionCatalogGetter):
short_name = "LatestCollectionCatalog"
def __init__(self) -> None:
self.catalog_type = gdb.lookup_type(
"mongo::(anonymous namespace)::LatestCollectionCatalog")
def __call__(self, decoration: gdb.Value, /) -> gdb.Value:
return shared_ptr_get(decoration["catalog"]).dereference()
# pylint: disable-next=too-few-public-methods
class _CollectionCatalogDecoration(CollectionCatalogGetter):
short_name = "CollectionCatalog"
def __init__(self) -> None:
self.catalog_type = gdb.lookup_type("mongo::CollectionCatalog")
def __call__(self, decoration: gdb.Value, /) -> gdb.Value:
return decoration
@classmethod
def from_service_context(cls, service_context: gdb.Value, /) -> "_CollectionCatalogPrinter":
"""Return a _CollectionCatalogPrinter from its decoration on ServiceContext."""
catalog_getter: CollectionCatalogGetter
try:
catalog_getter = cls._LatestCollectionCatalogDecoration()
except gdb.error as err:
if not err.args[0].startswith("No type named "):
raise
# The sole CollectionCatalog instance was previously a direct decoration on the global
# ServiceContext before becoming a versioned object in SERVER-52556.
# https://github.com/mongodb/mongo/blob/r4.4.13/src/mongo/db/catalog/collection_catalog.cpp#L47-L48
catalog_getter = cls._CollectionCatalogDecoration()
for decoration in DecorationIterator(service_context):
if decoration.type == catalog_getter.catalog_type:
catalog = catalog_getter(decoration)
break
else:
raise ValueError(
f"Failed to locate {catalog_getter.short_name} decoration in ServiceContext")
return cls(catalog)
# pylint: disable-next=too-few-public-methods
class ResourceIdFactoryGetter(typing.Protocol):
@abc.abstractmethod
def lookup_resource_mutex_label(self, res_id: gdb.Value, /) -> str:
"""Return the name of the mutex."""
raise NotImplementedError
@classmethod
def _lookup_resource_mutex_label_from(cls, res_id: gdb.Value, /, *,
all_labels: gdb.Value) -> str:
"""Return the name of the mutex from the specified vector of resource labels."""
xmethod_worker = stdlib_xmethods.VectorMethodsMatcher().match(all_labels.type, "at")
label = StdStringPrinter(xmethod_worker(all_labels, res_id)).string()
return label
# We don't have to_string() or children() defined on _ResourceCatalogPrinter right now. Until we
# have a sense of how else we might want to use the ResourceCatalog in GDB pretty printers, it is
# probably best to mark the type as being internal to this module.
class _ResourceCatalogPrinter(ServiceContextDecorationMixin, ResourceCatalogGetter,
ResourceIdFactoryGetter):
"""Pretty-printer for mongo::ResourceCatalog."""
def __init__(self, val: gdb.Value, /) -> None:
self.resources = val["_resources"]
self.val = val
def lookup_resource_name(self, res_id: gdb.Value, /) -> typing.Optional[str]:
"""Return the database or collection namespace string of the resource."""
for (iter_res_id, iter_nss_set) in AbslNodeHashMapPrinter(self.resources).items():
if iter_res_id == res_id:
nss_set = iter_nss_set
break
else:
return None
namespaces = [nss for (_, nss) in AbslFlatHashSetPrinter(nss_set).children()]
return StdStringPrinter(namespaces[0]).string() if len(namespaces) == 1 else None
@property
def resource_mutex_labels(self) -> gdb.Value:
"""Get the vector of resource mutex labels."""
# The ResourceCatalog::_mutexResourceIdLabels member was added as part of SERVER-77227.
# https://github.com/mongodb/mongo/blob/r7.1.0/src/mongo/db/concurrency/resource_catalog.h#L81
# We expose the instance attribute lazily here rather than defining it in __init__() because
# the member won't always exist.
return self.val["_mutexResourceIdLabels"]
def lookup_resource_mutex_label(self, res_id: gdb.Value, /) -> str:
return self._lookup_resource_mutex_label_from(res_id, all_labels=self.resource_mutex_labels)
@classmethod
def from_service_context(cls, service_context: gdb.Value, /) -> "_ResourceCatalogPrinter":
"""Return a _ResourceCatalogPrinter from its decoration on ServiceContext."""
try:
# The ResourceCatalog type was introduced and added as a decoration on the
# ServiceContext type as part of SERVER-67383 in MongoDB 6.2. Previously in MongoDB 6.0,
# the mapping of ResourceIds to collection and database names was managed through the
# CollectionCatalog.
resource_catalog_type = gdb.lookup_type("mongo::ResourceCatalog")
except gdb.error as err:
if not err.args[0].startswith("No type named "):
raise
raise ValueError(err.args[0]) from err
for decoration in DecorationIterator(service_context):
if decoration.type == resource_catalog_type:
return cls(decoration)
raise ValueError("Failed to locate ResourceCatalog decoration in ServiceContext")
@classmethod
def from_global(cls) -> "_ResourceCatalogPrinter":
"""Return a _ResourceCatalogPrinter from the function static ResourceCatalog."""
# The global ResourceCatalog was previously a decoration on the global ServiceContext before
# becoming a function static in SERVER-77227.
# https://github.com/mongodb/mongo/blob/r7.1.0/src/mongo/db/concurrency/resource_catalog.cpp#L52
if (resource_catalog :=
gdb_lookup_value("mongo::ResourceCatalog::get()::resourceCatalog")) is not None:
return cls(StaticImmortalPrinter(resource_catalog).value())
return cls.from_global_service_context()
# We don't have to_string() or children() defined on _DatabaseShardingStateMapPrinter right now.
# Until we have a sense of how else we might want to use the DatabaseShardingStateMap in GDB pretty
# printers, it is probably best to mark the type as being internal to this module.
class _DatabaseShardingStateMapPrinter(ServiceContextDecorationMixin):
"""Pretty-printer for mongo::DatabaseShardingStateMap."""
def __init__(self, val: gdb.Value, /) -> None:
self.databases = val["_databases"]
self.val = val
def lookup_database_name(self, res_id: gdb.Value, /) -> typing.Optional[str]:
"""Return the database name of the resource."""
for (_, dss) in AbslFlatHashMapPrinter(self.databases).items():
dss = shared_ptr_get(dss).dereference()
if dss["_stateChangeMutex"]["_rid"] == res_id:
return StdStringPrinter(dss["_dbName"]).string()
return None
@classmethod
def from_service_context(cls, service_context: gdb.Value,
/) -> "_DatabaseShardingStateMapPrinter":
"""Return a _DatabaseShardingStateMapPrinter from its decoration on ServiceContext."""
try:
# The DatabaseShardingStateMap type was introduced and added as a decoration on the
# ServiceContext type as part of SERVER-34431 in MongoDB 4.4. Previously in MongoDB 4.2,
# DatabaseShardingState was a decoration on each Database instance.
databases_type = gdb.lookup_type(
"mongo::(anonymous namespace)::DatabaseShardingStateMap")
except gdb.error as err:
if not err.args[0].startswith("No type named "):
raise
raise ValueError(err.args[0]) from err
for decoration in DecorationIterator(service_context):
if decoration.type == databases_type:
return cls(decoration)
raise ValueError("Failed to locate DatabaseShardingStateMap decoration in ServiceContext")
class LockManagerPrinter(PrettyPrinterProtocol, SupportsDisplayHint, ServiceContextDecorationMixin):
# pylint: disable=missing-function-docstring
"""Pretty-printer for mongo::LockManager."""
def __init__(self, val: gdb.Value, /) -> None:
self.buckets = val["_lockBuckets"]
self.val = val
# Static member variables are not always accessible within GDB from the instance value
# itself. We look up the fully qualified symbol rather than writing `val["_numLockBuckets"]`
# here to work around this limitation.
num_buckets = gdb_lookup_value("mongo::LockManager::_numLockBuckets")
assert num_buckets is not None
self.num_buckets = int(num_buckets)
@staticmethod
def display_hint() -> typing.Literal["map"]:
return "map"
def to_string(self) -> str:
# The LockManagerPrinter.children() method skips over resources which have no locks granted
# on them to match the behavior of mongo::LockManager::dump(). However, no output when
# calling `python print(lock_mgr.val)` would be confusing to users so we implement
# LockManagerPrinter.to_string() to make this situation more obvious. Unfortunately, the
# LockManager has no higher-level notion of whether a MODE_S or MODE_X lock request is
# present in the system so we may do the work of walking the lock buckets twice.
for _ in self.children():
return "mongo::LockManager dump"
return "mongo::LockManager dump (no strong locks held or pending)"
def children(self) -> typing.Iterator[typing.Tuple[str, gdb.Value]]:
for i in range(self.num_buckets):
bucket_data = AbslNodeHashMapPrinter(self.buckets[i]["data"])
for (res_id, lock_head_ptr) in bucket_data.items():
lock_head = lock_head_ptr.dereference()
# We skip displaying anything for resources which have no locks granted on them to
# match the behavior of mongo::LockManager::dump(). Resources which aren't held by
# anything thread cannot be involved in a deadlock because there could be any
# conflicts either.
if LockRequestListPrinter(lock_head["grantedList"]):
yield ("", res_id)
yield ("", lock_head)
@classmethod
def from_service_context(cls, service_context: gdb.Value, /) -> "LockManagerPrinter":
"""Return a LockManagerPrinter from its decoration on ServiceContext."""
lock_manager_type = gdb.lookup_type("mongo::LockManager")
for decoration in DecorationIterator(service_context):
if decoration.type == lock_manager_type:
lock_manager = decoration
break
else:
raise ValueError("Failed to locate LockManager decoration in ServiceContext")
return cls(lock_manager)
@classmethod
def from_global(cls) -> "LockManagerPrinter":
"""Return a LockManagerPrinter from the global LockManager."""
# The global LockManager was previously its own global before becoming a decoration on the
# global ServiceContext in SERVER-52516.
# https://github.com/mongodb/mongo/blob/r5.0.6/src/mongo/db/concurrency/lock_state.cpp#L122
if (lock_manager :=
gdb_lookup_value("mongo::(anonymous namespace)::globalLockManager")) is not None:
return cls(lock_manager)
return cls.from_global_service_context()
class LockRequestListPrinter(PrettyPrinterProtocol, SupportsDisplayHint):
# pylint: disable=missing-function-docstring
"""Pretty-printer for mongo::LockRequestList (doubly-linked list)."""
def __init__(self, val: gdb.Value, /) -> None:
self.val = val
@staticmethod
def display_hint() -> typing.Literal["array"]:
return "array"
def to_string(self) -> str:
return "mongo::LockRequestList" if self else "Empty mongo::LockRequestList"
def children(self) -> typing.Iterator[typing.Tuple[str, gdb.Value]]:
lock_request = self.val["_front"]
while lock_request != 0:
yield ("", lock_request.dereference())
lock_request = lock_request["next"]
def __bool__(self) -> bool:
"""Return True if the linked list isn't empty, and return False otherwise."""
return self.val["_front"] != 0
# pylint: disable-next=invalid-name
def ServiceContextClientsListIterator(service_context: gdb.Value, /) -> typing.Iterator[gdb.Value]:
"""Return a generator of every mongo::Client* in the given mongo::ServiceContext."""
try:
gdb.lookup_type("mongo::ServiceContext::ClientMap")
except gdb.error as err:
if not err.args[0].startswith("No type named "):
raise
for (_, client) in AbslNodeHashSetPrinter(service_context["_clients"]).children():
yield client
else:
for (client, _) in AbslNodeHashMapPrinter(service_context["_clients"]).items():
yield client
# pylint: disable-next=too-few-public-methods
class LockRequestPrinter(SupportsChildren):
# pylint: disable=missing-function-docstring
"""Pretty-printer for mongo::LockRequest."""
_cached_threads: typing.ClassVar[typing.Optional[typing.Dict[int, gdb.InferiorThread]]] = None
"""Mapping from the pthread_t address (std::thread::id) to the associated gdb.InferiorThread."""
_cached_operation_contexts: typing.ClassVar[typing.Optional[typing.Dict[int, gdb.Value]]] = None
"""Mapping from the mongo::Locker* address to the associated mongo::OperationContext*."""
def __init__(self, val: gdb.Value, /) -> None:
self.val = val
def children(self) -> typing.Iterator[typing.Tuple[str, typing.Union[str, gdb.Value]]]:
for field in self.val.type.fields():
assert field.name is not None, (
"Unexpected anonymous field in mongo::LockRequest struct")
assert not field.is_base_class, (
f"Unexpected base type '{field.name}' for mongo::LockRequest struct")
assert not field.artificial, (
f"Unexpected vtable '{field.name}' for mongo::LockRequest struct")
if field.bitpos is None:
# The mongo::LockRequest struct doesn't have any static members nor would it likely
# ever have any. But we defensively skip listing any static members here.
continue
data_member = self.val[field.name]
if field.name != "locker":
yield (field.name, data_member)
continue
locker = data_member.dereference()
locker_debug_info = locker["_debugInfo"]
# We augment the field name displayed for the mongo::LockRequest::locker member to
# include its type. GDB would otherwise only display the mongo::Locker* address.
yield (f"locker = ({locker.dynamic_type.pointer()}) {hex(int(locker.address))}",
data_member)
try:
# The mongo::LockerImpl type was consolidated with its mongo::Locker base class as
# part of SERVER-84753 in MongoDB 7.3.
locker_impl_type = gdb.lookup_type("mongo::LockerImpl")
except gdb.error as err:
if not err.args[0].startswith("No type named "):
raise
locker_impl_type = locker.type
if locker_impl_type == locker.dynamic_type:
thread_id = int(locker.cast(locker_impl_type)["_threadId"]["_M_thread"])
self._populate_cached_threads()
assert self._cached_threads is not None
if (thread := self._cached_threads.get(thread_id)) is not None:
thread_name = f', "{thread.name}"' if thread.name else ""
thread_id_str = f"{hex(thread_id)} (GDB Id: thread {thread.num}{thread_name})"
elif thread_id == 0:
thread_id_str = self._make_inactive_thread_hint(
StdStringPrinter(locker_debug_info).string())
else:
# Only if the libthread_db library is not available would we expect to not find
# the thread's ID.
assert not self._cached_threads, f"Failed to identify thread {hex(thread_id)}"
thread_id_str = hex(thread_id)
# gdb.Value.__str__() returns a Python string according to how the value would be
# **printed** by GDB. We therefore yield `thread_id_str` directly rather than
# gdb.Value(thread_id_str) to have the string text displayed verbatim.
yield ("locker._threadId", thread_id_str)
yield ("locker._debugInfo", locker_debug_info)
self._populate_cached_operation_contexts()
assert self._cached_operation_contexts is not None
if (operation_context :=
self._cached_operation_contexts.get(int(locker.address))) is not None:
# We augment the field name displayed for the mongo::LockRequest::$_opCtx
# pseudo-member to include its type. GDB would otherwise only display the
# mongo::OperationContext* address. The '$' character included in the field name was
# intentionally chosen as invalid C++ and GDB syntax to avoid any naming collisions.
yield (f"locker.$_opCtx = ({operation_context.type}) {hex(int(operation_context))}",
operation_context)
def _make_inactive_thread_hint(self, locker_debug_info_str: str) -> str:
if locker_debug_info_str.startswith("lsid: "):
# https://github.com/mongodb/mongo/blob/r8.0.0-rc4/src/mongo/db/transaction/transaction_participant.cpp#L1360-L1364
return "No active thread (likely an idle transaction)"
if " operationType: " in locker_debug_info_str:
# https://github.com/mongodb/mongo/blob/r8.0.0-rc4/src/mongo/db/s/sharding_ddl_coordinator.cpp#L336
return "No active thread (likely a ShardingDDLCoordinator)"
return "No active thread (Locker::unsetThreadId() was called)"
@classmethod
def _populate_cached_threads(cls) -> None:
if cls._cached_threads is not None:
# The cache has already been populated.
return
if not gdb_is_libthread_db_loaded():
# gdb.InferiorThread.handle() raises a RuntimeError when the libthread_db library is not
# available. In this situation, the pthread_t address (std::thread::id) cannot be known
# for the gdb.InferiorThread. We return early and leave `_cached_threads` as an empty
# dictionary to skip attempting to populate the cache later.
cls._cached_threads = {}
return
# Using native byte ordering is only correct here because the libthread_db library won't be
# available when cross-platform debugging.
fmt = "=Q"
cached_threads: typing.Dict[int, gdb.InferiorThread] = {}
for thread in gdb.selected_inferior().threads():
(thread_id, ) = struct.unpack_from(fmt, memoryview(thread.handle()))
assert isinstance(thread_id, int)
cached_threads[thread_id] = thread
cls._cached_threads = cached_threads
@classmethod
def _populate_cached_operation_contexts(cls) -> None:
if cls._cached_operation_contexts is not None:
# The cache has already been populated.
return
service_context = gdb_lookup_value("mongo::(anonymous namespace)::globalServiceContext")
assert service_context is not None
cached_operation_contexts: typing.Dict[int, gdb.Value] = {}
for client in ServiceContextClientsListIterator(service_context):
if (operation_context := client["_opCtx"]) != 0:
locker = operation_context["_locker"]
if (locker_address := unique_ptr_get(locker)) != 0:
cached_operation_contexts[int(locker_address)] = operation_context
cls._cached_operation_contexts = cached_operation_contexts
# We don't have to_string() or children() defined on _ResourceIdFactoryPrinter right now. Until we
# have a sense of how else we might want to use the ResourceIdFactory in GDB pretty printers, it is
# probably best to mark the type as being internal to this module.
class _ResourceIdFactoryPrinter(ResourceIdFactoryGetter):
"""Pretty-printer for mongo::Lock::ResourceMutex::ResourceIdFactory."""
def __init__(self, val: gdb.Value, /) -> None:
self.resource_labels = val["labels"]
self.val = val
def lookup_resource_mutex_label(self, res_id: gdb.Value, /) -> str:
return self._lookup_resource_mutex_label_from(res_id, all_labels=self.resource_labels)
@classmethod
def from_global(cls) -> "_ResourceIdFactoryPrinter":
"""Return a _ResourceIdFactoryPrinter from its global definition."""
# The ResourceIdFactory type was moved to be a nested type within mongo::Lock::ResourceMutex
# as part of SERVER-70467 in MongoDB 6.3. Previously in MongoDB 6.0, the ResourceIdFactory
# type was defined as its own top-level type.
# https://github.com/mongodb/mongo/blob/r7.0.0/src/mongo/db/concurrency/d_concurrency.cpp#L77
# https://github.com/mongodb/mongo/blob/r6.0.9/src/mongo/db/concurrency/d_concurrency.cpp#L91
if (res_id_factory := gdb_lookup_value(
# pylint: disable-next=line-too-long
"mongo::Lock::ResourceMutex::ResourceIdFactory::_resourceIdFactory()::resourceIdFactory"
)) is not None:
return cls(StaticImmortalPrinter(res_id_factory).value())
if (res_id_factory := gdb_lookup_value(
"mongo::(anonymous namespace)::ResourceIdFactory::resourceIdFactory")) is not None:
return cls(res_id_factory)
raise ValueError("Failed to locate ResourceIdFactory")
# pylint: disable-next=too-few-public-methods
class ResourceIdPrinter(SupportsToString):
# pylint: disable=missing-function-docstring
"""Pretty-printer for mongo::ResourceId."""
def __init__(self, val: gdb.Value, /) -> None:
self.val = val
self.full_hash = int(val["_fullHash"])
resource_type_bits = gdb_lookup_value("mongo::ResourceId::resourceTypeBits")
assert resource_type_bits is not None
self.resource_type = gdb.Value(self.full_hash >> (64 - int(resource_type_bits))).cast(
gdb.lookup_type("mongo::ResourceType"))
self.hash_id = self.full_hash & ((2**64 - 1) >> int(resource_type_bits))
def to_string(self) -> str:
ret = f"{{{self.full_hash}: {self.resource_type}, {self.hash_id}}}"
if self.resource_type == gdb_lookup_value("mongo::RESOURCE_MUTEX"):
res_id_factory: ResourceIdFactoryGetter
try:
res_id_factory = _ResourceIdFactoryPrinter.from_global()
except ValueError:
res_id_factory = _ResourceCatalogPrinter.from_global()
label = res_id_factory.lookup_resource_mutex_label(gdb.Value(self.hash_id))
ret += f", {label}"
if "DatabaseShardingState" == label:
# The label for the DatabaseShardingState's ResourceMutex was changed as part of
# SERVER-70610 in MongoDB 6.2 where it now embeds the database name in the label.
# Previously in MongoDB 6.0, the labels for all DatabaseShardingStates'
# ResourceMutexes were the same generic "DatabaseShardingState" string and meant
# consulting the DatabaseShardingStateMap was the only way to know which
# ResourceMutex corresponded to which DatabaseShardingState.
try:
dss_map = _DatabaseShardingStateMapPrinter.from_global_service_context()
except ValueError:
pass
else:
if (db_name := dss_map.lookup_database_name(self.val)) is not None:
ret += f", {db_name}"
if self.resource_type in (gdb_lookup_value("mongo::RESOURCE_DDL_DATABASE"),
gdb_lookup_value("mongo::RESOURCE_DDL_COLLECTION")):
# Names for the ScopedDatabaseDDLLock and ScopedCollectionDDLLock resources were added
# to the ResourceCatalog as part of SERVER-77512.
# https://github.com/mongodb/mongo/blob/r7.1.0/src/mongo/db/concurrency/resource_catalog.cpp#L66-L69
resource_catalog = _ResourceCatalogPrinter.from_global()
if (resource_name := resource_catalog.lookup_resource_name(self.val)) is not None:
ret += f", {resource_name}"
if self.resource_type in (gdb_lookup_value("mongo::RESOURCE_DATABASE"),
gdb_lookup_value("mongo::RESOURCE_COLLECTION")):
catalog: ResourceCatalogGetter
try:
catalog = _ResourceCatalogPrinter.from_global()
except ValueError:
catalog = _CollectionCatalogPrinter.from_global_service_context()
if (nss := catalog.lookup_resource_name(self.val)) is not None:
ret += f", {nss}"
if (self.resource_type == gdb_lookup_value("mongo::RESOURCE_GLOBAL")
and ResourceGlobalIdPrinter.is_type_defined()):
global_res_id = gdb.Value(self.hash_id).cast(gdb.lookup_type("mongo::ResourceGlobalId"))
ret += f", {global_res_id}"
return ret
# pylint: disable-next=too-few-public-methods
class ResourceTypePrinter(SupportsToString):
# pylint: disable=missing-function-docstring
"""Pretty-printer for mongo::ResourceType."""
@property
def resource_type_names(self) -> typing.Tuple[str, ...]:
# We duplicate the contents of mongo::ResourceTypeNames[] here for a couple reasons:
#
# 1. gdb.lookup_symbol("mongo::ResourceTypeNames") would OOM the GDB process when
# searching for the symbol in a dynamically-linked mongod executable.
#
# 2. While gdb.lookup_static_symbol("mongo::ResourceTypeNames").value() would avoid the
# memory pressure, that function is only available starting in the v4 toolchain.
# Moreover, the `const char*` values are almost surely to have been optimized out
# anyway.
global_resource_names = ("Global", ) if ResourceGlobalIdPrinter.is_type_defined() else (
"ParallelBatchWriterMode", "ReplicationStateTransition", "Global")
tenant_resource_name = (("Tenant", )
if gdb_lookup_value("mongo::RESOURCE_TENANT") is not None else ())
ddl_resource_names = (("DDLDatabase", "DDLCollection") if
gdb_lookup_value("mongo::RESOURCE_DDL_DATABASE") is not None else ())
return (("Invalid", ) + global_resource_names + tenant_resource_name +
("Database", "Collection", "Metadata") + ddl_resource_names + ("Mutex", ))
def __init__(self, val: gdb.Value, /) -> None:
self.val = val
def to_string(self) -> str:
return self.resource_type_names[int(self.val)]
class ResourceGlobalIdPrinter(SupportsToString):
# pylint: disable=missing-function-docstring
"""Pretty-printer for mongo::ResourceGlobalId."""
@property
def resource_global_id_names(self) -> typing.Tuple[str, ...]:
# We duplicate the contents of mongo::ResourceGlobalIdNames[] for the same reasons described
# above in ResourceTypePrinter.
# The parallel batch writer mutex was removed in MongoDB 7.1 as part of SERVER-38341.
pbwm_resource_name = ("ParallelBatchWriterMode", ) if gdb_lookup_value(
"mongo::resourceIdParallelBatchWriterMode") is not None else ()
# The feature compatibility version full transition lock was replaced with an equivalent
# barrier applied to only transaction operations in MongoDB 7.3 as part of SERVER-66340.
txn_barrier_resource_name = ("MultiDocumentTransactionsBarrier", ) if gdb_lookup_value(
"mongo::resourceIdMultiDocumentTransactionsBarrier") is not None else (
"FeatureCompatibilityVersion", )
return (pbwm_resource_name + txn_barrier_resource_name +
("ReplicationStateTransition", "Global"))
def __init__(self, val: gdb.Value, /) -> None:
self.val = val
def to_string(self) -> str:
return self.resource_global_id_names[int(self.val)]
@staticmethod
def is_type_defined() -> bool:
"""Return True if the ResourceGlobalId type is defined, and return False otherwise."""
try:
# The ResourceGlobalId type was introduced as part of SERVER-65821 in MongoDB 6.0 and
# then subsequently backported to 4.4.15 and 5.0.10. resourceIdParallelBatchWriterMode
# and resourceIdReplicationStateTransitionLock, along with a new
# resourceIdFeatureCompatibilityVersion, all became distinct resources under the
# RESOURCE_GLOBAL ResourceType. The top-level RESOURCE_PBWM and RESOURCE_RSTL
# ResourceTypes were removed.
gdb.lookup_type("mongo::ResourceGlobalId")
return True
except gdb.error as err:
if not err.args[0].startswith("No type named "):
raise
return False
def add_printers(pretty_printer: gdb.printing.RegexpCollectionPrettyPrinter, /) -> None:
"""Add the LockManager related printers to the pretty printer collection given."""
pretty_printer.add_printer("mongo::LockManager", "^mongo::LockManager$", LockManagerPrinter)
pretty_printer.add_printer("mongo::LockRequestList", "^mongo::LockRequestList$",
LockRequestListPrinter)
pretty_printer.add_printer("mongo::LockRequest", "^mongo::LockRequest$", LockRequestPrinter)
pretty_printer.add_printer("mongo::ResourceId", "^mongo::ResourceId$", ResourceIdPrinter)
pretty_printer.add_printer("mongo::ResourceType", "^mongo::ResourceType$", ResourceTypePrinter)
pretty_printer.add_printer("mongo::ResourceGlobalId", "^mongo::ResourceGlobalId$",
ResourceGlobalIdPrinter)