summaryrefslogtreecommitdiff
path: root/erts/emulator/beam/erl_lock_count.h
blob: 5af815dad849541f4b3341bce76083caac84b781 (plain)
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
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 2008-2020. All Rights Reserved.
 *
 * 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.
 *
 * %CopyrightEnd%
 */

/**
 * @description Statistics for locks.
 * @file erl_lock_count.h
 *
 * @author Björn-Egil Dahlberg
 * @author John Högberg
 *
 * Conceptual representation:
 *
 *     - set name
 *     | - id (the unique lock)
 *     | | - lock type
 *     | | - statistics
 *     | | | - location (file and line number)
 *     | | | - attempts
 *     | | | - collisions (including trylock busy)
 *     | | | - timer (time spent in waiting for lock)
 *     | | | - n_timer (collisions excluding trylock busy)
 *     | | | - histogram
 *     | | | | - # 0 = log2(lock wait_time ns)
 *     | | | | - ...
 *     | | | | - # n = log2(lock wait_time ns)
 *
 * Each instance of a lock is the unique lock, i.e. set and id in that set.
 * For each lock there is a set of statistics with where and what impact
 * the lock acquisition had.
 */

#ifndef ERTS_LOCK_COUNT_H__
#define ERTS_LOCK_COUNT_H__

#ifdef  ERTS_ENABLE_LOCK_COUNT
#ifndef ERTS_ENABLE_LOCK_POSITION
/** @brief Controls whether _x variants of mtx functions are used. */
#define ERTS_ENABLE_LOCK_POSITION 1
#endif

#include "sys.h"
#include "ethread.h"

#include "erl_term.h"
#include "erl_lock_flags.h"

#define ERTS_LCNT_MAX_LOCK_LOCATIONS  (5)

#define ERTS_LCNT_HISTOGRAM_MAX_NS    (((unsigned long)1LL << 28) - 1)
#if 0 || defined(ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT)
#define ERTS_LCNT_HISTOGRAM_SLOT_SIZE (30)
#define ERTS_LCNT_HISTOGRAM_RSHIFT    (0)
#else
#define ERTS_LCNT_HISTOGRAM_SLOT_SIZE (20)
#define ERTS_LCNT_HISTOGRAM_RSHIFT    (10)
#endif

typedef struct {
    unsigned long s;
    unsigned long ns;
} erts_lcnt_time_t;

typedef struct {
    /** @brief log2 array of nano seconds occurrences */
    Uint32 ns[ERTS_LCNT_HISTOGRAM_SLOT_SIZE];
} erts_lcnt_hist_t;

typedef struct {
    /** @brief In which file the lock was taken. May be NULL. */
    const char *file;
    /** @brief Line number in \c file */
    unsigned int line;

    /* "attempts" and "collisions" need to be atomic since try_lock busy does
     * not acquire a lock and there is no post action to rectify the
     * situation. */

    ethr_atomic_t attempts;
    ethr_atomic_t collisions;

    erts_lcnt_time_t total_time_waited;
    Uint64 times_waited;

    erts_lcnt_hist_t wait_time_histogram;
} erts_lcnt_lock_stats_t;

typedef struct lcnt_lock_info_t_ {
    erts_lock_flags_t flags;
    const char *name;
    /** @brief Id if possible, must be an immediate */
    Eterm id;

    /* The first entry is reserved as a fallback for when location information
     * is missing, and when the lock is used in more than (MAX_LOCK_LOCATIONS
     * - 1) different places. */
    erts_lcnt_lock_stats_t location_stats[ERTS_LCNT_MAX_LOCK_LOCATIONS];
    unsigned int location_count;

    /* -- Everything below is internal to this module ---------------------- */

    /* Lock states; rw locks uses both states, other locks only uses w_state */

    /** @brief Write state. 0 = not taken, otherwise n threads waiting */
    ethr_atomic_t w_state;
    /** @brief Read state. 0 = not taken, > 0 -> writes will wait */
    ethr_atomic_t r_state;

    struct lcnt_lock_info_t_ *prev;
    struct lcnt_lock_info_t_ *next;

    /** @brief Used in place of erts_refc_t to avoid a circular dependency. */
    ethr_atomic_t ref_count;
    ethr_atomic32_t lock;

    /** @brief Deletion hook called once \c ref_count reaches 0; may defer
     * deletion by modifying \c ref_count. */
    void (*dispose)(struct lcnt_lock_info_t_ *);

    struct lcnt_lock_info_carrier_ *carrier;
} erts_lcnt_lock_info_t;

typedef struct lcnt_lock_info_list_ {
    erts_lcnt_lock_info_t head;
    erts_lcnt_lock_info_t tail;
} erts_lcnt_lock_info_list_t;

typedef struct {
    erts_lcnt_time_t timer_start; /**< Time of last clear */
    erts_lcnt_time_t duration; /**< Time since last clear */

    erts_lcnt_lock_info_list_t *current_locks;
    erts_lcnt_lock_info_list_t *deleted_locks;
} erts_lcnt_data_t;

typedef struct lcnt_lock_info_carrier_ erts_lcnt_lock_info_carrier_t;

typedef ethr_atomic_t erts_lcnt_ref_t;

/* -- Globals -------------------------------------------------------------- */

/** @brief Checks whether counting is enabled for any of the given
 * categories. */
#define erts_lcnt_check_enabled(flags) \
    (lcnt_category_mask__ & flags)

/* -- Lock operations ------------------------------------------------------
 *
 * All of these will nop if there's nothing "installed" on the given reference,
 * in order to transparently support enable/disable at runtime. */

/** @brief Records that a lock is being acquired. */
ERTS_GLB_FORCE_INLINE
void erts_lcnt_lock(erts_lcnt_ref_t *ref);

/** @copydoc erts_lcnt_lock
 * @param option Notes whether the lock is a read or write lock. */
ERTS_GLB_FORCE_INLINE
void erts_lcnt_lock_opt(erts_lcnt_ref_t *ref, erts_lock_options_t option);

/** @brief Records that a lock has been acquired. */
ERTS_GLB_FORCE_INLINE
void erts_lcnt_lock_post(erts_lcnt_ref_t *ref);

/** @copydoc erts_lcnt_lock_post.
 * @param file The name of the file where the lock was acquired.
 * @param line The line at which the lock was acquired. */
ERTS_GLB_FORCE_INLINE
void erts_lcnt_lock_post_x(erts_lcnt_ref_t *ref, const char *file, unsigned int line);

/** @brief Records that a lock has been released. */
ERTS_GLB_FORCE_INLINE
void erts_lcnt_unlock(erts_lcnt_ref_t *ref);

/** @copydoc erts_lcnt_unlock_opt
 * @param option Whether the lock is a read or write lock. */
ERTS_GLB_FORCE_INLINE
void erts_lcnt_unlock_opt(erts_lcnt_ref_t *ref, erts_lock_options_t option);

/** @brief Rectifies the case where a lock wasn't actually a lock operation.
 *
 * Only used for process locks at the moment. */
ERTS_GLB_FORCE_INLINE
void erts_lcnt_lock_unacquire(erts_lcnt_ref_t *ref);

/** @brief Records the result of a trylock, placing the queried lock status in
 * \c result. */
ERTS_GLB_FORCE_INLINE
void erts_lcnt_trylock(erts_lcnt_ref_t *ref, int result);

/** @copydoc erts_lcnt_trylock
 * @param option Whether the lock is a read or write lock. */
ERTS_GLB_FORCE_INLINE
void erts_lcnt_trylock_opt(erts_lcnt_ref_t *ref, int result, erts_lock_options_t option);

/* Indexed variants of the standard lock operations, for use when a single
 * reference contains many counters (eg. process locks).
 *
 * erts_lcnt_open_ref must be used to safely extract the installed carrier,
 * which must released with erts_lcnt_close_reference on success.
 *
 * Refer to \c erts_lcnt_lock for example usage. */

ERTS_GLB_INLINE
void erts_lcnt_lock_idx(erts_lcnt_lock_info_carrier_t *carrier, int index);
ERTS_GLB_INLINE
void erts_lcnt_lock_opt_idx(erts_lcnt_lock_info_carrier_t *carrier, int index, erts_lock_options_t option);

ERTS_GLB_INLINE
void erts_lcnt_lock_post_idx(erts_lcnt_lock_info_carrier_t *carrier, int index);
ERTS_GLB_INLINE
void erts_lcnt_lock_post_x_idx(erts_lcnt_lock_info_carrier_t *carrier, int index, const char *file, unsigned int line);

ERTS_GLB_INLINE
void erts_lcnt_lock_unacquire_idx(erts_lcnt_lock_info_carrier_t *carrier, int index);

ERTS_GLB_INLINE
void erts_lcnt_unlock_idx(erts_lcnt_lock_info_carrier_t *carrier, int index);
ERTS_GLB_INLINE
void erts_lcnt_unlock_opt_idx(erts_lcnt_lock_info_carrier_t *carrier, int index, erts_lock_options_t option);

ERTS_GLB_INLINE
void erts_lcnt_trylock_idx(erts_lcnt_lock_info_carrier_t *carrier, int index, int result);
ERTS_GLB_INLINE
void erts_lcnt_trylock_opt_idx(erts_lcnt_lock_info_carrier_t *carrier, int index, int result, erts_lock_options_t option);

/* -- Reference operations ------------------------------------------------- */

/** @brief Registers a lock counter reference; this must be called prior to
 * using any other functions in this module. */
ERTS_GLB_INLINE
void erts_lcnt_init_ref(erts_lcnt_ref_t *ref);

/** @brief As \c erts_lcnt_init_ref, but also enables lock counting right
 * away if appropriate to reduce noise.
 * @param id An immediate erlang term with whatever extra data you want to
 * identify this lock with. */
ERTS_GLB_INLINE
void erts_lcnt_init_ref_x(erts_lcnt_ref_t *ref, const char *name,
    Eterm id, erts_lock_flags_t flags);

/** @brief Checks whether counting is enabled on the given reference. */
ERTS_GLB_FORCE_INLINE
int erts_lcnt_check_ref_installed(erts_lcnt_ref_t *ref);

/** @brief Convenience macro to re/enable counting on an already initialized
 * reference. Don't forget to specify the lock type in \c flags! */
#define erts_lcnt_install_new_lock_info(ref, name, id, flags) \
    if(!erts_lcnt_check_ref_installed(ref)) { \
        erts_lcnt_lock_info_carrier_t *__carrier; \
        __carrier = erts_lcnt_create_lock_info_carrier(1);\
        erts_lcnt_init_lock_info_idx(__carrier, 0, name, id, flags); \
        erts_lcnt_install(ref, __carrier);\
    } while(0)

erts_lcnt_lock_info_carrier_t *erts_lcnt_create_lock_info_carrier(int count);

/** @brief Initializes the lock info at the given index.
 * @param id An immediate erlang term with whatever extra data you want to
 * identify this lock with.
 * @param flags The flags the lock itself was initialized with. Keep in mind
 * that all locks in a carrier must share the same category/static property. */
ERTS_GLB_INLINE
void erts_lcnt_init_lock_info_idx(erts_lcnt_lock_info_carrier_t *carrier, int index,
    const char *name, Eterm id, erts_lock_flags_t flags);

/** @brief Atomically installs the given lock counters. Nops (and releases the
 * provided carrier) if something was already installed. */
void erts_lcnt_install(erts_lcnt_ref_t *ref, erts_lcnt_lock_info_carrier_t *carrier);

/** @brief Atomically removes the currently installed lock counters. Nops if
 * nothing was installed. */
void erts_lcnt_uninstall(erts_lcnt_ref_t *ref);

ERTS_GLB_FORCE_INLINE
int erts_lcnt_open_ref(erts_lcnt_ref_t *ref, int *handle, erts_lcnt_lock_info_carrier_t **result);

ERTS_GLB_FORCE_INLINE
void erts_lcnt_close_ref(int handle, erts_lcnt_lock_info_carrier_t *carrier);

/* -- Module initialization ------------------------------------------------ */

void erts_lcnt_pre_thr_init(void);
void erts_lcnt_post_thr_init(void);
void erts_lcnt_late_init(void);

/** @brief Called after everything in the system has been initialized,
 * including the schedulers. This is mainly a backwards compatibility shim for
 * matching the old lcnt behavior where all lock counting was enabled by
 * default. */
void erts_lcnt_post_startup(void);

void erts_lcnt_thread_setup(void);
void erts_lcnt_thread_exit_handler(void);

/* -- BIF interface -------------------------------------------------------- */

/** @brief Safely iterates through all entries in the given list.
 *
 * The referenced item will be valid until the next call to
 * \c erts_lcnt_iterate_list after which point it may be destroyed; call
 * erts_lcnt_retain_lock_info if you wish to hang on to it beyond that point.
 *
 * Iteration can be cancelled by calling erts_lcnt_release_lock_info on the
 * iterator and breaking out of the loop.
 *
 * @param iterator The iteration variable; set the pointee to NULL to start
 * iteration.
 * @return 1 while the iterator is valid, 0 at the end of the list. */
int erts_lcnt_iterate_list(erts_lcnt_lock_info_list_t *list, erts_lcnt_lock_info_t **iterator);

/** @brief Clears the counter state of all locks, and releases all locks
 * preserved through erts_lcnt_set_preserve_info (if any). */
void erts_lcnt_clear_counters(void);

/** @brief Retrieves the global lock counter state.
 *
 * Note that the lists may be modified while you're mucking around with them.
 * Always use \c erts_lcnt_iterate_list to enumerate them. */
erts_lcnt_data_t erts_lcnt_get_data(void);

void erts_lcnt_retain_lock_info(erts_lcnt_lock_info_t *info);
void erts_lcnt_release_lock_info(erts_lcnt_lock_info_t *info);

/** @brief Sets whether to preserve the info of destroyed/uninstalled locks.
 *
 * This option makes no distinction whether the lock was destroyed or if lock
 * counting was simply disabled, so erts_lcnt_set_category_mask must not be
 * used while this option is active. */
void erts_lcnt_set_preserve_info(int enable);

int erts_lcnt_get_preserve_info(void);

/** @brief Updates the category mask, enabling or disabling counting on the
 * affected locks as necessary.
 *
 * This is not guaranteed to find all existing locks; only those that are
 * flagged as static locks and those reachable through other means can be
 * altered. */
void erts_lcnt_set_category_mask(erts_lock_flags_t mask);

erts_lock_flags_t erts_lcnt_get_category_mask(void);

/* -- Inline implementation ------------------------------------------------ */

/* The following is a hack to get the things we need from erl_thr_progress.h,
 * which we can't #include without dependency hell breaking loose.
 *
 * The size of LcntThrPrgrLaterOp and value of the constant are verified at
 * compile-time in erts_lcnt_pre_thr_init. */

int lcnt_thr_progress_unmanaged_delay__(void);
void lcnt_thr_progress_unmanaged_continue__(int handle);
typedef struct { Uint64 _[4]; } LcntThrPrgrLaterOp;
#define LCNT_THR_PRGR_DHANDLE_MANAGED -1

struct lcnt_lock_info_carrier_ {
    ethr_atomic_t ref_count;

    LcntThrPrgrLaterOp release_entries;

    unsigned char entry_count;
    erts_lcnt_lock_info_t entries[];
};

typedef struct {
    erts_lcnt_time_t timer; /* timer */
    int timer_set;          /* bool */
    int lock_in_conflict;   /* bool */
} lcnt_thread_data_t__;

extern const int lcnt_log2_tab64__[];

extern ethr_tsd_key lcnt_thr_data_key__;
extern erts_lock_flags_t lcnt_category_mask__;

#ifdef DEBUG
extern int lcnt_initialization_completed__;
#endif

void lcnt_register_static_lock__(erts_lcnt_ref_t *reference, const char *name, Eterm id,
    erts_lock_flags_t flags);

void lcnt_deallocate_carrier__(erts_lcnt_lock_info_carrier_t *carrier);

ERTS_GLB_INLINE
int lcnt_log2__(Uint64 v);

ERTS_GLB_INLINE
void lcnt_update_wait_histogram__(erts_lcnt_hist_t *hist, erts_lcnt_time_t *time_waited);

ERTS_GLB_INLINE
void lcnt_update_stats__(erts_lcnt_lock_stats_t *stats, int lock_in_conflict, erts_lcnt_time_t *time_waited);

ERTS_GLB_INLINE
erts_lcnt_lock_stats_t *lcnt_get_lock_stats__(erts_lcnt_lock_info_t *info, const char *file, unsigned int line);

ERTS_GLB_INLINE
void lcnt_dec_lock_state__(ethr_atomic_t *l_state);

ERTS_GLB_INLINE
void lcnt_time__(erts_lcnt_time_t *time);

ERTS_GLB_INLINE
void lcnt_time_add__(erts_lcnt_time_t *t, erts_lcnt_time_t *d);

ERTS_GLB_INLINE
void lcnt_time_diff__(erts_lcnt_time_t *d, erts_lcnt_time_t *t1, erts_lcnt_time_t *t0);

ERTS_GLB_INLINE
void lcnt_retain_carrier__(erts_lcnt_lock_info_carrier_t *carrier);

ERTS_GLB_INLINE
void lcnt_release_carrier__(erts_lcnt_lock_info_carrier_t *carrier);

ERTS_GLB_INLINE
lcnt_thread_data_t__ *lcnt_get_thread_data__(void);

#if ERTS_GLB_INLINE_INCL_FUNC_DEF

ERTS_GLB_INLINE
void lcnt_time__(erts_lcnt_time_t *time) {
    /*
     * erts_sys_hrtime() is the highest resolution
     * we could find, it may or may not be monotonic...
     */
    ErtsMonotonicTime mtime = erts_sys_hrtime();
    time->s  = (unsigned long) (mtime / 1000000000LL);
    time->ns = (unsigned long) (mtime - 1000000000LL*time->s);
}

/* difference d must be non-negative */

ERTS_GLB_INLINE
void lcnt_time_add__(erts_lcnt_time_t *t, erts_lcnt_time_t *d) {
    t->s  += d->s;
    t->ns += d->ns;

    t->s  += t->ns / 1000000000LL;
    t->ns  = t->ns % 1000000000LL;
}

ERTS_GLB_INLINE
void lcnt_time_diff__(erts_lcnt_time_t *d, erts_lcnt_time_t *t1, erts_lcnt_time_t *t0) {
    long ds;
    long dns;

    ds  = t1->s  - t0->s;
    dns = t1->ns - t0->ns;

    /* the difference should not be able to get bigger than 1 sec in ns*/

    if (dns < 0) {
        ds  -= 1;
        dns += 1000000000LL;
    }

    ASSERT(ds >= 0);

    d->s  = ds;
    d->ns = dns;
}

ERTS_GLB_INLINE
int lcnt_log2__(Uint64 v) {
    v |= v >> 1;
    v |= v >> 2;
    v |= v >> 4;
    v |= v >> 8;
    v |= v >> 16;
    v |= v >> 32;

    return lcnt_log2_tab64__[((Uint64)((v - (v >> 1))*0x07EDD5E59A4E28C2)) >> 58];
}

ERTS_GLB_INLINE
void lcnt_update_wait_histogram__(erts_lcnt_hist_t *hist, erts_lcnt_time_t *time_waited) {
    int idx;

    if(time_waited->s > 0 || time_waited->ns > ERTS_LCNT_HISTOGRAM_MAX_NS) {
        idx = ERTS_LCNT_HISTOGRAM_SLOT_SIZE - 1;
    } else {
        unsigned long r = time_waited->ns >> ERTS_LCNT_HISTOGRAM_RSHIFT;

        idx = r ? lcnt_log2__(r) : 0;
    }

    hist->ns[idx]++;
}

ERTS_GLB_INLINE
void lcnt_update_stats__(erts_lcnt_lock_stats_t *stats, int lock_in_conflict, erts_lcnt_time_t *time_waited) {
    ethr_atomic_inc(&stats->attempts);

    if(lock_in_conflict) {
        ethr_atomic_inc(&stats->collisions);
    }

    if(time_waited) {
        stats->times_waited++;

        lcnt_time_add__(&stats->total_time_waited, time_waited);
        lcnt_update_wait_histogram__(&stats->wait_time_histogram, time_waited);
    }
}

/* If we were installed while the lock was held, r/w_state will be 0 and we
 * can't tell which unlock or unacquire operation was the last. To get around
 * this we assume that all excess operations go *towards* zero rather than down
 * to zero, eventually becoming consistent with the actual state once the lock
 * is fully released.
 *
 * Conflicts might not be counted until the recorded state is fully consistent
 * with the actual state, but there should be no other ill effects. */

ERTS_GLB_INLINE
void lcnt_dec_lock_state__(ethr_atomic_t *l_state) {
    ethr_sint_t state = ethr_atomic_dec_read_acqb(l_state);

    /* We cannot assume that state is >= -1 here; unlock and unacquire might
     * bring it below -1 and race to increment it back. */

    if(state < 0) {
        ethr_atomic_inc_acqb(l_state);
    }
}

ERTS_GLB_INLINE
erts_lcnt_lock_stats_t *lcnt_get_lock_stats__(erts_lcnt_lock_info_t *info, const char *file, unsigned int line) {
    unsigned int i;

    ASSERT(info->location_count >= 1 && info->location_count <= ERTS_LCNT_MAX_LOCK_LOCATIONS);

    for(i = 0; i < info->location_count; i++) {
        erts_lcnt_lock_stats_t *stats = &info->location_stats[i];

        if(stats->file == file && stats->line == line) {
            return stats;
        }
    }

    if(info->location_count < ERTS_LCNT_MAX_LOCK_LOCATIONS) {
        erts_lcnt_lock_stats_t *stats = &info->location_stats[info->location_count];

        stats->file = file;
        stats->line = line;

        info->location_count++;

        return stats;
    }

    return &info->location_stats[0];
}

ERTS_GLB_INLINE
lcnt_thread_data_t__ *lcnt_get_thread_data__(void) {
    lcnt_thread_data_t__ *eltd = (lcnt_thread_data_t__ *)ethr_tsd_get(lcnt_thr_data_key__);

    ASSERT(eltd);

    return eltd;
}

ERTS_GLB_FORCE_INLINE
int erts_lcnt_open_ref(erts_lcnt_ref_t *ref, int *handle, erts_lcnt_lock_info_carrier_t **result) {
    if(ERTS_LIKELY(!erts_lcnt_check_ref_installed(ref))) {
        return 0;
    }

    ASSERT(lcnt_initialization_completed__);

    (*handle) = lcnt_thr_progress_unmanaged_delay__();
    (*result) = (erts_lcnt_lock_info_carrier_t*)ethr_atomic_read(ref);

    if(*result) {
        if(*handle != LCNT_THR_PRGR_DHANDLE_MANAGED) {
            lcnt_retain_carrier__(*result);
            lcnt_thr_progress_unmanaged_continue__(*handle);
        }

        return 1;
    } else if(*handle != LCNT_THR_PRGR_DHANDLE_MANAGED) {
        lcnt_thr_progress_unmanaged_continue__(*handle);
    }

    return 0;
}

ERTS_GLB_FORCE_INLINE
void erts_lcnt_close_ref(int handle, erts_lcnt_lock_info_carrier_t *carrier) {
    if(handle != LCNT_THR_PRGR_DHANDLE_MANAGED) {
        lcnt_release_carrier__(carrier);
    }
}

ERTS_GLB_INLINE
void erts_lcnt_init_ref(erts_lcnt_ref_t *ref) {
    ethr_atomic_init(ref, (ethr_sint_t)NULL);
}

ERTS_GLB_INLINE
void erts_lcnt_init_ref_x(erts_lcnt_ref_t *ref, const char *name,
                          Eterm id, erts_lock_flags_t flags) {
    erts_lcnt_init_ref(ref);

    if(flags & ERTS_LOCK_FLAGS_PROPERTY_STATIC) {
        lcnt_register_static_lock__(ref, name, id, flags);
    }

    if(erts_lcnt_check_enabled(flags)) {
        erts_lcnt_install_new_lock_info(ref, name, id, flags);
    }
}

ERTS_GLB_FORCE_INLINE
int erts_lcnt_check_ref_installed(erts_lcnt_ref_t *ref) {
    return (!!*ethr_atomic_addr(ref));
}

ERTS_GLB_FORCE_INLINE
void erts_lcnt_lock(erts_lcnt_ref_t *ref) {
    erts_lcnt_lock_info_carrier_t *carrier;
    int handle;

    if(erts_lcnt_open_ref(ref, &handle, &carrier)) {
        erts_lcnt_lock_idx(carrier, 0);

        erts_lcnt_close_ref(handle, carrier);
    }
}

ERTS_GLB_FORCE_INLINE
void erts_lcnt_lock_opt(erts_lcnt_ref_t *ref, erts_lock_options_t option) {
    erts_lcnt_lock_info_carrier_t *carrier;
    int handle;

    if(erts_lcnt_open_ref(ref, &handle, &carrier)) {
        erts_lcnt_lock_opt_idx(carrier, 0, option);

        erts_lcnt_close_ref(handle, carrier);
    }
}

ERTS_GLB_FORCE_INLINE
void erts_lcnt_lock_post(erts_lcnt_ref_t *ref) {
    erts_lcnt_lock_info_carrier_t *carrier;
    int handle;

    if(erts_lcnt_open_ref(ref, &handle, &carrier)) {
        erts_lcnt_lock_post_idx(carrier, 0);

        erts_lcnt_close_ref(handle, carrier);
    }
}

ERTS_GLB_FORCE_INLINE
void erts_lcnt_lock_post_x(erts_lcnt_ref_t *ref, const char *file, unsigned int line) {
    erts_lcnt_lock_info_carrier_t *carrier;
    int handle;

    if(erts_lcnt_open_ref(ref, &handle, &carrier)) {
        erts_lcnt_lock_post_x_idx(carrier, 0, file, line);

        erts_lcnt_close_ref(handle, carrier);
    }
}

ERTS_GLB_FORCE_INLINE
void erts_lcnt_lock_unacquire(erts_lcnt_ref_t *ref) {
    erts_lcnt_lock_info_carrier_t *carrier;
    int handle;

    if(erts_lcnt_open_ref(ref, &handle, &carrier)) {
        erts_lcnt_lock_unacquire_idx(carrier, 0);

        erts_lcnt_close_ref(handle, carrier);
    }
}

ERTS_GLB_FORCE_INLINE
void erts_lcnt_unlock(erts_lcnt_ref_t *ref) {
    erts_lcnt_lock_info_carrier_t *carrier;
    int handle;

    if(erts_lcnt_open_ref(ref, &handle, &carrier)) {
        erts_lcnt_unlock_idx(carrier, 0);

        erts_lcnt_close_ref(handle, carrier);
    }
}

ERTS_GLB_FORCE_INLINE
void erts_lcnt_unlock_opt(erts_lcnt_ref_t *ref, erts_lock_options_t option) {
    erts_lcnt_lock_info_carrier_t *carrier;
    int handle;

    if(erts_lcnt_open_ref(ref, &handle, &carrier)) {
        erts_lcnt_unlock_opt_idx(carrier, 0, option);

        erts_lcnt_close_ref(handle, carrier);
    }
}

ERTS_GLB_FORCE_INLINE
void erts_lcnt_trylock(erts_lcnt_ref_t *ref, int result) {
    erts_lcnt_lock_info_carrier_t *carrier;
    int handle;

    if(erts_lcnt_open_ref(ref, &handle, &carrier)) {
        erts_lcnt_trylock_idx(carrier, 0, result);

        erts_lcnt_close_ref(handle, carrier);
    }
}

ERTS_GLB_FORCE_INLINE
void erts_lcnt_trylock_opt(erts_lcnt_ref_t *ref, int result, erts_lock_options_t option) {
    erts_lcnt_lock_info_carrier_t *carrier;
    int handle;

    if(erts_lcnt_open_ref(ref, &handle, &carrier)) {
        erts_lcnt_trylock_opt_idx(carrier, 0, result, option);

        erts_lcnt_close_ref(handle, carrier);
    }
}

ERTS_GLB_INLINE
void erts_lcnt_lock_idx(erts_lcnt_lock_info_carrier_t *carrier, int index) {
    erts_lcnt_lock_opt_idx(carrier, index, ERTS_LOCK_OPTIONS_WRITE);
}

ERTS_GLB_INLINE
void erts_lcnt_lock_opt_idx(erts_lcnt_lock_info_carrier_t *carrier, int index, erts_lock_options_t option) {
    erts_lcnt_lock_info_t *info = &carrier->entries[index];

    lcnt_thread_data_t__ *eltd = lcnt_get_thread_data__();

    ASSERT(index < carrier->entry_count);

    ASSERT((option & ERTS_LOCK_OPTIONS_READ) || (option & ERTS_LOCK_OPTIONS_WRITE));

    if(option & ERTS_LOCK_OPTIONS_WRITE) {
        ethr_sint_t w_state, r_state;

        w_state = ethr_atomic_inc_read(&info->w_state) - 1;
        r_state = ethr_atomic_read(&info->r_state);

        /* We cannot acquire w_lock if either w or r are taken */
        eltd->lock_in_conflict = (w_state > 0) || (r_state > 0);
    } else {
        ethr_sint_t w_state = ethr_atomic_read(&info->w_state);

        /* We cannot acquire r_lock if w_lock is taken */
        eltd->lock_in_conflict = (w_state > 0);
    }

    if(option & ERTS_LOCK_OPTIONS_READ) {
        ASSERT(info->flags & ERTS_LOCK_FLAGS_PROPERTY_READ_WRITE);
        ethr_atomic_inc(&info->r_state);
    }

    if(eltd->lock_in_conflict) {
        /* Only set the timer if nobody else has it. This should only happen
         * when proc_locks acquires several locks "atomically." All other locks
         * will block the thread when locked (w_state > 0) */
        if(eltd->timer_set == 0) {
            lcnt_time__(&eltd->timer);
        }

        eltd->timer_set++;
    }
}

ERTS_GLB_INLINE
void erts_lcnt_lock_post_idx(erts_lcnt_lock_info_carrier_t *carrier, int index) {
    erts_lcnt_lock_post_x_idx(carrier, index, NULL, 0);
}

ERTS_GLB_INLINE
void erts_lcnt_lock_post_x_idx(erts_lcnt_lock_info_carrier_t *carrier, int index, const char *file, unsigned int line) {
    erts_lcnt_lock_info_t *info = &carrier->entries[index];

    lcnt_thread_data_t__ *eltd = lcnt_get_thread_data__();
    erts_lcnt_lock_stats_t *stats;

    ASSERT(index < carrier->entry_count);

    /* If the lock was in conflict, update the time spent waiting. */
    stats = lcnt_get_lock_stats__(info, file, line);
    if(eltd->timer_set) {
        erts_lcnt_time_t time_wait;
        erts_lcnt_time_t timer;

        lcnt_time__(&timer);

        lcnt_time_diff__(&time_wait, &timer, &eltd->timer);
        lcnt_update_stats__(stats, eltd->lock_in_conflict, &time_wait);

        eltd->timer_set--;

        ASSERT(eltd->timer_set >= 0);
    } else {
        lcnt_update_stats__(stats, eltd->lock_in_conflict, NULL);
    }
}

ERTS_GLB_INLINE
void erts_lcnt_unlock_idx(erts_lcnt_lock_info_carrier_t *carrier, int index) {
    ASSERT(index < carrier->entry_count);

    erts_lcnt_unlock_opt_idx(carrier, index, ERTS_LOCK_OPTIONS_WRITE);
}

ERTS_GLB_INLINE
void erts_lcnt_unlock_opt_idx(erts_lcnt_lock_info_carrier_t *carrier, int index, erts_lock_options_t option) {
    erts_lcnt_lock_info_t *info = &carrier->entries[index];

    ASSERT(index < carrier->entry_count);

    ASSERT((option & ERTS_LOCK_OPTIONS_READ) || (option & ERTS_LOCK_OPTIONS_WRITE));

    if(option & ERTS_LOCK_OPTIONS_WRITE) {
        lcnt_dec_lock_state__(&info->w_state);
    }

    if(option & ERTS_LOCK_OPTIONS_READ) {
        ASSERT(info->flags & ERTS_LOCK_FLAGS_PROPERTY_READ_WRITE);
        lcnt_dec_lock_state__(&info->r_state);
    }
}

ERTS_GLB_INLINE
void erts_lcnt_lock_unacquire_idx(erts_lcnt_lock_info_carrier_t *carrier, int index) {
    erts_lcnt_lock_info_t *info = &carrier->entries[index];

    ASSERT(index < carrier->entry_count);

    lcnt_dec_lock_state__(&info->w_state);
}

ERTS_GLB_INLINE
void erts_lcnt_trylock_idx(erts_lcnt_lock_info_carrier_t *carrier, int index, int result) {
    ASSERT(index < carrier->entry_count);

    erts_lcnt_trylock_opt_idx(carrier, index, result, ERTS_LOCK_OPTIONS_WRITE);
}

ERTS_GLB_INLINE
void erts_lcnt_trylock_opt_idx(erts_lcnt_lock_info_carrier_t *carrier, int index, int result, erts_lock_options_t option) {
    erts_lcnt_lock_info_t *info = &carrier->entries[index];

    ASSERT(index < carrier->entry_count);

    ASSERT((option & ERTS_LOCK_OPTIONS_READ) || (option & ERTS_LOCK_OPTIONS_WRITE));

    if(result != EBUSY) {
        if(option & ERTS_LOCK_OPTIONS_WRITE) {
            ethr_atomic_inc(&info->w_state);
        }

        if(option & ERTS_LOCK_OPTIONS_READ) {
            ASSERT(info->flags & ERTS_LOCK_FLAGS_PROPERTY_READ_WRITE);
            ethr_atomic_inc(&info->r_state);
        }

        lcnt_update_stats__(&info->location_stats[0], 0, NULL);
    } else {
        ethr_atomic_inc(&info->location_stats[0].attempts);
        ethr_atomic_inc(&info->location_stats[0].collisions);
    }
}

ERTS_GLB_INLINE
void erts_lcnt_init_lock_info_idx(erts_lcnt_lock_info_carrier_t *carrier, int index,
                                  const char *name, Eterm id, erts_lock_flags_t flags) {
    erts_lcnt_lock_info_t *info = &carrier->entries[index];

    ASSERT(is_immed(id));

    ASSERT(flags & ERTS_LOCK_FLAGS_MASK_TYPE);
    ASSERT(flags & ERTS_LOCK_FLAGS_MASK_CATEGORY);

    info->flags = flags;
    info->name = name;
    info->id = id;
}

ERTS_GLB_INLINE
void lcnt_retain_carrier__(erts_lcnt_lock_info_carrier_t *carrier) {
#ifdef DEBUG
    ASSERT(ethr_atomic_inc_read_acqb(&carrier->ref_count) >= 2);
#else
    ethr_atomic_inc_acqb(&carrier->ref_count);
#endif
}

ERTS_GLB_INLINE
void lcnt_release_carrier__(erts_lcnt_lock_info_carrier_t *carrier) {
    ethr_sint_t count = ethr_atomic_dec_read_relb(&carrier->ref_count);

    ASSERT(count >= 0);

    if(count == 0) {
        lcnt_deallocate_carrier__(carrier);
    }
}

#endif

#endif /* ifdef  ERTS_ENABLE_LOCK_COUNT  */
#endif /* ifndef ERTS_LOCK_COUNT_H__     */