~drizzle-trunk/drizzle/development

« back to all changes in this revision

Viewing changes to storage/innobase/trx/trx0purge.c

  • Committer: Brian Aker
  • Date: 2009-04-17 01:45:33 UTC
  • Revision ID: brian@gaz-20090417014533-exdrtriab9zecqs2
Refactor get_variable to session

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*****************************************************************************
 
2
 
 
3
Copyright (c) 1996, 2009, Innobase Oy. All Rights Reserved.
 
4
 
 
5
This program is free software; you can redistribute it and/or modify it under
 
6
the terms of the GNU General Public License as published by the Free Software
 
7
Foundation; version 2 of the License.
 
8
 
 
9
This program is distributed in the hope that it will be useful, but WITHOUT
 
10
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
11
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 
12
 
 
13
You should have received a copy of the GNU General Public License along with
 
14
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 
15
Place, Suite 330, Boston, MA 02111-1307 USA
 
16
 
 
17
*****************************************************************************/
 
18
 
 
19
/******************************************************
 
20
Purge old versions
 
21
 
 
22
Created 3/26/1996 Heikki Tuuri
 
23
*******************************************************/
 
24
 
 
25
#include "trx0purge.h"
 
26
 
 
27
#ifdef UNIV_NONINL
 
28
#include "trx0purge.ic"
 
29
#endif
 
30
 
 
31
#include "fsp0fsp.h"
 
32
#include "mach0data.h"
 
33
#include "trx0rseg.h"
 
34
#include "trx0trx.h"
 
35
#include "trx0roll.h"
 
36
#include "read0read.h"
 
37
#include "fut0fut.h"
 
38
#include "que0que.h"
 
39
#include "row0purge.h"
 
40
#include "row0upd.h"
 
41
#include "trx0rec.h"
 
42
#include "srv0que.h"
 
43
#include "os0thread.h"
 
44
 
 
45
/* The global data structure coordinating a purge */
 
46
UNIV_INTERN trx_purge_t*        purge_sys = NULL;
 
47
 
 
48
/* A dummy undo record used as a return value when we have a whole undo log
 
49
which needs no purge */
 
50
UNIV_INTERN trx_undo_rec_t      trx_purge_dummy_rec;
 
51
 
 
52
/*********************************************************************
 
53
Checks if trx_id is >= purge_view: then it is guaranteed that its update
 
54
undo log still exists in the system. */
 
55
UNIV_INTERN
 
56
ibool
 
57
trx_purge_update_undo_must_exist(
 
58
/*=============================*/
 
59
                        /* out: TRUE if is sure that it is preserved, also
 
60
                        if the function returns FALSE, it is possible that
 
61
                        the undo log still exists in the system */
 
62
        dulint  trx_id) /* in: transaction id */
 
63
{
 
64
#ifdef UNIV_SYNC_DEBUG
 
65
        ut_ad(rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED));
 
66
#endif /* UNIV_SYNC_DEBUG */
 
67
 
 
68
        if (!read_view_sees_trx_id(purge_sys->view, trx_id)) {
 
69
 
 
70
                return(TRUE);
 
71
        }
 
72
 
 
73
        return(FALSE);
 
74
}
 
75
 
 
76
/*=================== PURGE RECORD ARRAY =============================*/
 
77
 
 
78
/***********************************************************************
 
79
Stores info of an undo log record during a purge. */
 
80
static
 
81
trx_undo_inf_t*
 
82
trx_purge_arr_store_info(
 
83
/*=====================*/
 
84
                        /* out: pointer to the storage cell */
 
85
        dulint  trx_no, /* in: transaction number */
 
86
        dulint  undo_no)/* in: undo number */
 
87
{
 
88
        trx_undo_inf_t* cell;
 
89
        trx_undo_arr_t* arr;
 
90
        ulint           i;
 
91
 
 
92
        arr = purge_sys->arr;
 
93
 
 
94
        for (i = 0;; i++) {
 
95
                cell = trx_undo_arr_get_nth_info(arr, i);
 
96
 
 
97
                if (!(cell->in_use)) {
 
98
                        /* Not in use, we may store here */
 
99
                        cell->undo_no = undo_no;
 
100
                        cell->trx_no = trx_no;
 
101
                        cell->in_use = TRUE;
 
102
 
 
103
                        arr->n_used++;
 
104
 
 
105
                        return(cell);
 
106
                }
 
107
        }
 
108
}
 
109
 
 
110
/***********************************************************************
 
111
Removes info of an undo log record during a purge. */
 
112
UNIV_INLINE
 
113
void
 
114
trx_purge_arr_remove_info(
 
115
/*======================*/
 
116
        trx_undo_inf_t* cell)   /* in: pointer to the storage cell */
 
117
{
 
118
        trx_undo_arr_t* arr;
 
119
 
 
120
        arr = purge_sys->arr;
 
121
 
 
122
        cell->in_use = FALSE;
 
123
 
 
124
        ut_ad(arr->n_used > 0);
 
125
 
 
126
        arr->n_used--;
 
127
}
 
128
 
 
129
/***********************************************************************
 
130
Gets the biggest pair of a trx number and an undo number in a purge array. */
 
131
static
 
132
void
 
133
trx_purge_arr_get_biggest(
 
134
/*======================*/
 
135
        trx_undo_arr_t* arr,    /* in: purge array */
 
136
        dulint*         trx_no, /* out: transaction number: ut_dulint_zero
 
137
                                if array is empty */
 
138
        dulint*         undo_no)/* out: undo number */
 
139
{
 
140
        trx_undo_inf_t* cell;
 
141
        dulint          pair_trx_no;
 
142
        dulint          pair_undo_no;
 
143
        int             trx_cmp;
 
144
        ulint           n_used;
 
145
        ulint           i;
 
146
        ulint           n;
 
147
 
 
148
        n = 0;
 
149
        n_used = arr->n_used;
 
150
        pair_trx_no = ut_dulint_zero;
 
151
        pair_undo_no = ut_dulint_zero;
 
152
 
 
153
        for (i = 0;; i++) {
 
154
                cell = trx_undo_arr_get_nth_info(arr, i);
 
155
 
 
156
                if (cell->in_use) {
 
157
                        n++;
 
158
                        trx_cmp = ut_dulint_cmp(cell->trx_no, pair_trx_no);
 
159
 
 
160
                        if ((trx_cmp > 0)
 
161
                            || ((trx_cmp == 0)
 
162
                                && (ut_dulint_cmp(cell->undo_no,
 
163
                                                  pair_undo_no) >= 0))) {
 
164
 
 
165
                                pair_trx_no = cell->trx_no;
 
166
                                pair_undo_no = cell->undo_no;
 
167
                        }
 
168
                }
 
169
 
 
170
                if (n == n_used) {
 
171
                        *trx_no = pair_trx_no;
 
172
                        *undo_no = pair_undo_no;
 
173
 
 
174
                        return;
 
175
                }
 
176
        }
 
177
}
 
178
 
 
179
/********************************************************************
 
180
Builds a purge 'query' graph. The actual purge is performed by executing
 
181
this query graph. */
 
182
static
 
183
que_t*
 
184
trx_purge_graph_build(void)
 
185
/*=======================*/
 
186
                                /* out, own: the query graph */
 
187
{
 
188
        mem_heap_t*     heap;
 
189
        que_fork_t*     fork;
 
190
        que_thr_t*      thr;
 
191
        /*      que_thr_t*      thr2; */
 
192
 
 
193
        heap = mem_heap_create(512);
 
194
        fork = que_fork_create(NULL, NULL, QUE_FORK_PURGE, heap);
 
195
        fork->trx = purge_sys->trx;
 
196
 
 
197
        thr = que_thr_create(fork, heap);
 
198
 
 
199
        thr->child = row_purge_node_create(thr, heap);
 
200
 
 
201
        /*      thr2 = que_thr_create(fork, fork, heap);
 
202
 
 
203
        thr2->child = row_purge_node_create(fork, thr2, heap);   */
 
204
 
 
205
        return(fork);
 
206
}
 
207
 
 
208
/************************************************************************
 
209
Creates the global purge system control structure and inits the history
 
210
mutex. */
 
211
UNIV_INTERN
 
212
void
 
213
trx_purge_sys_create(void)
 
214
/*======================*/
 
215
{
 
216
        ut_ad(mutex_own(&kernel_mutex));
 
217
 
 
218
        purge_sys = mem_alloc(sizeof(trx_purge_t));
 
219
 
 
220
        purge_sys->state = TRX_STOP_PURGE;
 
221
 
 
222
        purge_sys->n_pages_handled = 0;
 
223
 
 
224
        purge_sys->purge_trx_no = ut_dulint_zero;
 
225
        purge_sys->purge_undo_no = ut_dulint_zero;
 
226
        purge_sys->next_stored = FALSE;
 
227
 
 
228
        rw_lock_create(&purge_sys->latch, SYNC_PURGE_LATCH);
 
229
 
 
230
        mutex_create(&purge_sys->mutex, SYNC_PURGE_SYS);
 
231
 
 
232
        purge_sys->heap = mem_heap_create(256);
 
233
 
 
234
        purge_sys->arr = trx_undo_arr_create();
 
235
 
 
236
        purge_sys->sess = sess_open();
 
237
 
 
238
        purge_sys->trx = purge_sys->sess->trx;
 
239
 
 
240
        purge_sys->trx->is_purge = 1;
 
241
 
 
242
        ut_a(trx_start_low(purge_sys->trx, ULINT_UNDEFINED));
 
243
 
 
244
        purge_sys->query = trx_purge_graph_build();
 
245
 
 
246
        purge_sys->view = read_view_oldest_copy_or_open_new(ut_dulint_zero,
 
247
                                                            purge_sys->heap);
 
248
}
 
249
 
 
250
/*================ UNDO LOG HISTORY LIST =============================*/
 
251
 
 
252
/************************************************************************
 
253
Adds the update undo log as the first log in the history list. Removes the
 
254
update undo log segment from the rseg slot if it is too big for reuse. */
 
255
UNIV_INTERN
 
256
void
 
257
trx_purge_add_update_undo_to_history(
 
258
/*=================================*/
 
259
        trx_t*  trx,            /* in: transaction */
 
260
        page_t* undo_page,      /* in: update undo log header page,
 
261
                                x-latched */
 
262
        mtr_t*  mtr)            /* in: mtr */
 
263
{
 
264
        trx_undo_t*     undo;
 
265
        trx_rseg_t*     rseg;
 
266
        trx_rsegf_t*    rseg_header;
 
267
        trx_usegf_t*    seg_header;
 
268
        trx_ulogf_t*    undo_header;
 
269
        trx_upagef_t*   page_header;
 
270
        ulint           hist_size;
 
271
 
 
272
        undo = trx->update_undo;
 
273
 
 
274
        ut_ad(undo);
 
275
 
 
276
        rseg = undo->rseg;
 
277
 
 
278
        ut_ad(mutex_own(&(rseg->mutex)));
 
279
 
 
280
        rseg_header = trx_rsegf_get(rseg->space, rseg->zip_size,
 
281
                                    rseg->page_no, mtr);
 
282
 
 
283
        undo_header = undo_page + undo->hdr_offset;
 
284
        seg_header  = undo_page + TRX_UNDO_SEG_HDR;
 
285
        page_header = undo_page + TRX_UNDO_PAGE_HDR;
 
286
 
 
287
        if (undo->state != TRX_UNDO_CACHED) {
 
288
                /* The undo log segment will not be reused */
 
289
 
 
290
                if (undo->id >= TRX_RSEG_N_SLOTS) {
 
291
                        fprintf(stderr,
 
292
                                "InnoDB: Error: undo->id is %lu\n",
 
293
                                (ulong) undo->id);
 
294
                        ut_error;
 
295
                }
 
296
 
 
297
                trx_rsegf_set_nth_undo(rseg_header, undo->id, FIL_NULL, mtr);
 
298
 
 
299
                hist_size = mtr_read_ulint(rseg_header + TRX_RSEG_HISTORY_SIZE,
 
300
                                           MLOG_4BYTES, mtr);
 
301
                ut_ad(undo->size == flst_get_len(
 
302
                              seg_header + TRX_UNDO_PAGE_LIST, mtr));
 
303
 
 
304
                mlog_write_ulint(rseg_header + TRX_RSEG_HISTORY_SIZE,
 
305
                                 hist_size + undo->size, MLOG_4BYTES, mtr);
 
306
        }
 
307
 
 
308
        /* Add the log as the first in the history list */
 
309
        flst_add_first(rseg_header + TRX_RSEG_HISTORY,
 
310
                       undo_header + TRX_UNDO_HISTORY_NODE, mtr);
 
311
        mutex_enter(&kernel_mutex);
 
312
        trx_sys->rseg_history_len++;
 
313
        mutex_exit(&kernel_mutex);
 
314
 
 
315
        /* Write the trx number to the undo log header */
 
316
        mlog_write_dulint(undo_header + TRX_UNDO_TRX_NO, trx->no, mtr);
 
317
        /* Write information about delete markings to the undo log header */
 
318
 
 
319
        if (!undo->del_marks) {
 
320
                mlog_write_ulint(undo_header + TRX_UNDO_DEL_MARKS, FALSE,
 
321
                                 MLOG_2BYTES, mtr);
 
322
        }
 
323
 
 
324
        if (rseg->last_page_no == FIL_NULL) {
 
325
 
 
326
                rseg->last_page_no = undo->hdr_page_no;
 
327
                rseg->last_offset = undo->hdr_offset;
 
328
                rseg->last_trx_no = trx->no;
 
329
                rseg->last_del_marks = undo->del_marks;
 
330
        }
 
331
}
 
332
 
 
333
/**************************************************************************
 
334
Frees an undo log segment which is in the history list. Cuts the end of the
 
335
history list at the youngest undo log in this segment. */
 
336
static
 
337
void
 
338
trx_purge_free_segment(
 
339
/*===================*/
 
340
        trx_rseg_t*     rseg,           /* in: rollback segment */
 
341
        fil_addr_t      hdr_addr,       /* in: the file address of log_hdr */
 
342
        ulint           n_removed_logs) /* in: count of how many undo logs we
 
343
                                        will cut off from the end of the
 
344
                                        history list */
 
345
{
 
346
        page_t*         undo_page;
 
347
        trx_rsegf_t*    rseg_hdr;
 
348
        trx_ulogf_t*    log_hdr;
 
349
        trx_usegf_t*    seg_hdr;
 
350
        ibool           freed;
 
351
        ulint           seg_size;
 
352
        ulint           hist_size;
 
353
        ibool           marked          = FALSE;
 
354
        mtr_t           mtr;
 
355
 
 
356
        /*      fputs("Freeing an update undo log segment\n", stderr); */
 
357
 
 
358
        ut_ad(mutex_own(&(purge_sys->mutex)));
 
359
loop:
 
360
        mtr_start(&mtr);
 
361
        mutex_enter(&(rseg->mutex));
 
362
 
 
363
        rseg_hdr = trx_rsegf_get(rseg->space, rseg->zip_size,
 
364
                                 rseg->page_no, &mtr);
 
365
 
 
366
        undo_page = trx_undo_page_get(rseg->space, rseg->zip_size,
 
367
                                      hdr_addr.page, &mtr);
 
368
        seg_hdr = undo_page + TRX_UNDO_SEG_HDR;
 
369
        log_hdr = undo_page + hdr_addr.boffset;
 
370
 
 
371
        /* Mark the last undo log totally purged, so that if the system
 
372
        crashes, the tail of the undo log will not get accessed again. The
 
373
        list of pages in the undo log tail gets inconsistent during the
 
374
        freeing of the segment, and therefore purge should not try to access
 
375
        them again. */
 
376
 
 
377
        if (!marked) {
 
378
                mlog_write_ulint(log_hdr + TRX_UNDO_DEL_MARKS, FALSE,
 
379
                                 MLOG_2BYTES, &mtr);
 
380
                marked = TRUE;
 
381
        }
 
382
 
 
383
        freed = fseg_free_step_not_header(seg_hdr + TRX_UNDO_FSEG_HEADER,
 
384
                                          &mtr);
 
385
        if (!freed) {
 
386
                mutex_exit(&(rseg->mutex));
 
387
                mtr_commit(&mtr);
 
388
 
 
389
                goto loop;
 
390
        }
 
391
 
 
392
        /* The page list may now be inconsistent, but the length field
 
393
        stored in the list base node tells us how big it was before we
 
394
        started the freeing. */
 
395
 
 
396
        seg_size = flst_get_len(seg_hdr + TRX_UNDO_PAGE_LIST, &mtr);
 
397
 
 
398
        /* We may free the undo log segment header page; it must be freed
 
399
        within the same mtr as the undo log header is removed from the
 
400
        history list: otherwise, in case of a database crash, the segment
 
401
        could become inaccessible garbage in the file space. */
 
402
 
 
403
        flst_cut_end(rseg_hdr + TRX_RSEG_HISTORY,
 
404
                     log_hdr + TRX_UNDO_HISTORY_NODE, n_removed_logs, &mtr);
 
405
 
 
406
        mutex_enter(&kernel_mutex);
 
407
        ut_ad(trx_sys->rseg_history_len >= n_removed_logs);
 
408
        trx_sys->rseg_history_len -= n_removed_logs;
 
409
        mutex_exit(&kernel_mutex);
 
410
 
 
411
        freed = FALSE;
 
412
 
 
413
        while (!freed) {
 
414
                /* Here we assume that a file segment with just the header
 
415
                page can be freed in a few steps, so that the buffer pool
 
416
                is not flooded with bufferfixed pages: see the note in
 
417
                fsp0fsp.c. */
 
418
 
 
419
                freed = fseg_free_step(seg_hdr + TRX_UNDO_FSEG_HEADER,
 
420
                                       &mtr);
 
421
        }
 
422
 
 
423
        hist_size = mtr_read_ulint(rseg_hdr + TRX_RSEG_HISTORY_SIZE,
 
424
                                   MLOG_4BYTES, &mtr);
 
425
        ut_ad(hist_size >= seg_size);
 
426
 
 
427
        mlog_write_ulint(rseg_hdr + TRX_RSEG_HISTORY_SIZE,
 
428
                         hist_size - seg_size, MLOG_4BYTES, &mtr);
 
429
 
 
430
        ut_ad(rseg->curr_size >= seg_size);
 
431
 
 
432
        rseg->curr_size -= seg_size;
 
433
 
 
434
        mutex_exit(&(rseg->mutex));
 
435
 
 
436
        mtr_commit(&mtr);
 
437
}
 
438
 
 
439
/************************************************************************
 
440
Removes unnecessary history data from a rollback segment. */
 
441
static
 
442
void
 
443
trx_purge_truncate_rseg_history(
 
444
/*============================*/
 
445
        trx_rseg_t*     rseg,           /* in: rollback segment */
 
446
        dulint          limit_trx_no,   /* in: remove update undo logs whose
 
447
                                        trx number is < limit_trx_no */
 
448
        dulint          limit_undo_no)  /* in: if transaction number is equal
 
449
                                        to limit_trx_no, truncate undo records
 
450
                                        with undo number < limit_undo_no */
 
451
{
 
452
        fil_addr_t      hdr_addr;
 
453
        fil_addr_t      prev_hdr_addr;
 
454
        trx_rsegf_t*    rseg_hdr;
 
455
        page_t*         undo_page;
 
456
        trx_ulogf_t*    log_hdr;
 
457
        trx_usegf_t*    seg_hdr;
 
458
        int             cmp;
 
459
        ulint           n_removed_logs  = 0;
 
460
        mtr_t           mtr;
 
461
 
 
462
        ut_ad(mutex_own(&(purge_sys->mutex)));
 
463
 
 
464
        mtr_start(&mtr);
 
465
        mutex_enter(&(rseg->mutex));
 
466
 
 
467
        rseg_hdr = trx_rsegf_get(rseg->space, rseg->zip_size,
 
468
                                 rseg->page_no, &mtr);
 
469
 
 
470
        hdr_addr = trx_purge_get_log_from_hist(
 
471
                flst_get_last(rseg_hdr + TRX_RSEG_HISTORY, &mtr));
 
472
loop:
 
473
        if (hdr_addr.page == FIL_NULL) {
 
474
 
 
475
                mutex_exit(&(rseg->mutex));
 
476
 
 
477
                mtr_commit(&mtr);
 
478
 
 
479
                return;
 
480
        }
 
481
 
 
482
        undo_page = trx_undo_page_get(rseg->space, rseg->zip_size,
 
483
                                      hdr_addr.page, &mtr);
 
484
 
 
485
        log_hdr = undo_page + hdr_addr.boffset;
 
486
 
 
487
        cmp = ut_dulint_cmp(mach_read_from_8(log_hdr + TRX_UNDO_TRX_NO),
 
488
                            limit_trx_no);
 
489
        if (cmp == 0) {
 
490
                trx_undo_truncate_start(rseg, rseg->space, hdr_addr.page,
 
491
                                        hdr_addr.boffset, limit_undo_no);
 
492
        }
 
493
 
 
494
        if (cmp >= 0) {
 
495
                mutex_enter(&kernel_mutex);
 
496
                ut_a(trx_sys->rseg_history_len >= n_removed_logs);
 
497
                trx_sys->rseg_history_len -= n_removed_logs;
 
498
                mutex_exit(&kernel_mutex);
 
499
 
 
500
                flst_truncate_end(rseg_hdr + TRX_RSEG_HISTORY,
 
501
                                  log_hdr + TRX_UNDO_HISTORY_NODE,
 
502
                                  n_removed_logs, &mtr);
 
503
 
 
504
                mutex_exit(&(rseg->mutex));
 
505
                mtr_commit(&mtr);
 
506
 
 
507
                return;
 
508
        }
 
509
 
 
510
        prev_hdr_addr = trx_purge_get_log_from_hist(
 
511
                flst_get_prev_addr(log_hdr + TRX_UNDO_HISTORY_NODE, &mtr));
 
512
        n_removed_logs++;
 
513
 
 
514
        seg_hdr = undo_page + TRX_UNDO_SEG_HDR;
 
515
 
 
516
        if ((mach_read_from_2(seg_hdr + TRX_UNDO_STATE) == TRX_UNDO_TO_PURGE)
 
517
            && (mach_read_from_2(log_hdr + TRX_UNDO_NEXT_LOG) == 0)) {
 
518
 
 
519
                /* We can free the whole log segment */
 
520
 
 
521
                mutex_exit(&(rseg->mutex));
 
522
                mtr_commit(&mtr);
 
523
 
 
524
                trx_purge_free_segment(rseg, hdr_addr, n_removed_logs);
 
525
 
 
526
                n_removed_logs = 0;
 
527
        } else {
 
528
                mutex_exit(&(rseg->mutex));
 
529
                mtr_commit(&mtr);
 
530
        }
 
531
 
 
532
        mtr_start(&mtr);
 
533
        mutex_enter(&(rseg->mutex));
 
534
 
 
535
        rseg_hdr = trx_rsegf_get(rseg->space, rseg->zip_size,
 
536
                                 rseg->page_no, &mtr);
 
537
 
 
538
        hdr_addr = prev_hdr_addr;
 
539
 
 
540
        goto loop;
 
541
}
 
542
 
 
543
/************************************************************************
 
544
Removes unnecessary history data from rollback segments. NOTE that when this
 
545
function is called, the caller must not have any latches on undo log pages! */
 
546
static
 
547
void
 
548
trx_purge_truncate_history(void)
 
549
/*============================*/
 
550
{
 
551
        trx_rseg_t*     rseg;
 
552
        dulint          limit_trx_no;
 
553
        dulint          limit_undo_no;
 
554
 
 
555
        ut_ad(mutex_own(&(purge_sys->mutex)));
 
556
 
 
557
        trx_purge_arr_get_biggest(purge_sys->arr, &limit_trx_no,
 
558
                                  &limit_undo_no);
 
559
 
 
560
        if (ut_dulint_is_zero(limit_trx_no)) {
 
561
 
 
562
                limit_trx_no = purge_sys->purge_trx_no;
 
563
                limit_undo_no = purge_sys->purge_undo_no;
 
564
        }
 
565
 
 
566
        /* We play safe and set the truncate limit at most to the purge view
 
567
        low_limit number, though this is not necessary */
 
568
 
 
569
        if (ut_dulint_cmp(limit_trx_no, purge_sys->view->low_limit_no) >= 0) {
 
570
                limit_trx_no = purge_sys->view->low_limit_no;
 
571
                limit_undo_no = ut_dulint_zero;
 
572
        }
 
573
 
 
574
        ut_ad((ut_dulint_cmp(limit_trx_no,
 
575
                             purge_sys->view->low_limit_no) <= 0));
 
576
 
 
577
        rseg = UT_LIST_GET_FIRST(trx_sys->rseg_list);
 
578
 
 
579
        while (rseg) {
 
580
                trx_purge_truncate_rseg_history(rseg, limit_trx_no,
 
581
                                                limit_undo_no);
 
582
                rseg = UT_LIST_GET_NEXT(rseg_list, rseg);
 
583
        }
 
584
}
 
585
 
 
586
/************************************************************************
 
587
Does a truncate if the purge array is empty. NOTE that when this function is
 
588
called, the caller must not have any latches on undo log pages! */
 
589
UNIV_INLINE
 
590
ibool
 
591
trx_purge_truncate_if_arr_empty(void)
 
592
/*=================================*/
 
593
                        /* out: TRUE if array empty */
 
594
{
 
595
        ut_ad(mutex_own(&(purge_sys->mutex)));
 
596
 
 
597
        if (purge_sys->arr->n_used == 0) {
 
598
 
 
599
                trx_purge_truncate_history();
 
600
 
 
601
                return(TRUE);
 
602
        }
 
603
 
 
604
        return(FALSE);
 
605
}
 
606
 
 
607
/***************************************************************************
 
608
Updates the last not yet purged history log info in rseg when we have purged
 
609
a whole undo log. Advances also purge_sys->purge_trx_no past the purged log. */
 
610
static
 
611
void
 
612
trx_purge_rseg_get_next_history_log(
 
613
/*================================*/
 
614
        trx_rseg_t*     rseg)   /* in: rollback segment */
 
615
{
 
616
        page_t*         undo_page;
 
617
        trx_ulogf_t*    log_hdr;
 
618
        trx_usegf_t*    seg_hdr;
 
619
        fil_addr_t      prev_log_addr;
 
620
        dulint          trx_no;
 
621
        ibool           del_marks;
 
622
        mtr_t           mtr;
 
623
 
 
624
        ut_ad(mutex_own(&(purge_sys->mutex)));
 
625
 
 
626
        mutex_enter(&(rseg->mutex));
 
627
 
 
628
        ut_a(rseg->last_page_no != FIL_NULL);
 
629
 
 
630
        purge_sys->purge_trx_no = ut_dulint_add(rseg->last_trx_no, 1);
 
631
        purge_sys->purge_undo_no = ut_dulint_zero;
 
632
        purge_sys->next_stored = FALSE;
 
633
 
 
634
        mtr_start(&mtr);
 
635
 
 
636
        undo_page = trx_undo_page_get_s_latched(rseg->space, rseg->zip_size,
 
637
                                                rseg->last_page_no, &mtr);
 
638
        log_hdr = undo_page + rseg->last_offset;
 
639
        seg_hdr = undo_page + TRX_UNDO_SEG_HDR;
 
640
 
 
641
        /* Increase the purge page count by one for every handled log */
 
642
 
 
643
        purge_sys->n_pages_handled++;
 
644
 
 
645
        prev_log_addr = trx_purge_get_log_from_hist(
 
646
                flst_get_prev_addr(log_hdr + TRX_UNDO_HISTORY_NODE, &mtr));
 
647
        if (prev_log_addr.page == FIL_NULL) {
 
648
                /* No logs left in the history list */
 
649
 
 
650
                rseg->last_page_no = FIL_NULL;
 
651
 
 
652
                mutex_exit(&(rseg->mutex));
 
653
                mtr_commit(&mtr);
 
654
 
 
655
                mutex_enter(&kernel_mutex);
 
656
 
 
657
                /* Add debug code to track history list corruption reported
 
658
                on the MySQL mailing list on Nov 9, 2004. The fut0lst.c
 
659
                file-based list was corrupt. The prev node pointer was
 
660
                FIL_NULL, even though the list length was over 8 million nodes!
 
661
                We assume that purge truncates the history list in moderate
 
662
                size pieces, and if we here reach the head of the list, the
 
663
                list cannot be longer than 20 000 undo logs now. */
 
664
 
 
665
                if (trx_sys->rseg_history_len > 20000) {
 
666
                        ut_print_timestamp(stderr);
 
667
                        fprintf(stderr,
 
668
                                "  InnoDB: Warning: purge reached the"
 
669
                                " head of the history list,\n"
 
670
                                "InnoDB: but its length is still"
 
671
                                " reported as %lu! Make a detailed bug\n"
 
672
                                "InnoDB: report, and submit it"
 
673
                                " to http://bugs.mysql.com\n",
 
674
                                (ulong) trx_sys->rseg_history_len);
 
675
                }
 
676
 
 
677
                mutex_exit(&kernel_mutex);
 
678
 
 
679
                return;
 
680
        }
 
681
 
 
682
        mutex_exit(&(rseg->mutex));
 
683
        mtr_commit(&mtr);
 
684
 
 
685
        /* Read the trx number and del marks from the previous log header */
 
686
        mtr_start(&mtr);
 
687
 
 
688
        log_hdr = trx_undo_page_get_s_latched(rseg->space, rseg->zip_size,
 
689
                                              prev_log_addr.page, &mtr)
 
690
                + prev_log_addr.boffset;
 
691
 
 
692
        trx_no = mach_read_from_8(log_hdr + TRX_UNDO_TRX_NO);
 
693
 
 
694
        del_marks = mach_read_from_2(log_hdr + TRX_UNDO_DEL_MARKS);
 
695
 
 
696
        mtr_commit(&mtr);
 
697
 
 
698
        mutex_enter(&(rseg->mutex));
 
699
 
 
700
        rseg->last_page_no = prev_log_addr.page;
 
701
        rseg->last_offset = prev_log_addr.boffset;
 
702
        rseg->last_trx_no = trx_no;
 
703
        rseg->last_del_marks = del_marks;
 
704
 
 
705
        mutex_exit(&(rseg->mutex));
 
706
}
 
707
 
 
708
/***************************************************************************
 
709
Chooses the next undo log to purge and updates the info in purge_sys. This
 
710
function is used to initialize purge_sys when the next record to purge is
 
711
not known, and also to update the purge system info on the next record when
 
712
purge has handled the whole undo log for a transaction. */
 
713
static
 
714
void
 
715
trx_purge_choose_next_log(void)
 
716
/*===========================*/
 
717
{
 
718
        trx_undo_rec_t* rec;
 
719
        trx_rseg_t*     rseg;
 
720
        trx_rseg_t*     min_rseg;
 
721
        dulint          min_trx_no;
 
722
        ulint           space = 0;   /* remove warning (??? bug ???) */
 
723
        ulint           zip_size = 0;
 
724
        ulint           page_no = 0; /* remove warning (??? bug ???) */
 
725
        ulint           offset = 0;  /* remove warning (??? bug ???) */
 
726
        mtr_t           mtr;
 
727
 
 
728
        ut_ad(mutex_own(&(purge_sys->mutex)));
 
729
        ut_ad(purge_sys->next_stored == FALSE);
 
730
 
 
731
        rseg = UT_LIST_GET_FIRST(trx_sys->rseg_list);
 
732
 
 
733
        min_trx_no = ut_dulint_max;
 
734
 
 
735
        min_rseg = NULL;
 
736
 
 
737
        while (rseg) {
 
738
                mutex_enter(&(rseg->mutex));
 
739
 
 
740
                if (rseg->last_page_no != FIL_NULL) {
 
741
 
 
742
                        if ((min_rseg == NULL)
 
743
                            || (ut_dulint_cmp(min_trx_no,
 
744
                                              rseg->last_trx_no) > 0)) {
 
745
 
 
746
                                min_rseg = rseg;
 
747
                                min_trx_no = rseg->last_trx_no;
 
748
                                space = rseg->space;
 
749
                                zip_size = rseg->zip_size;
 
750
                                ut_a(space == 0); /* We assume in purge of
 
751
                                                  externally stored fields
 
752
                                                  that space id == 0 */
 
753
                                page_no = rseg->last_page_no;
 
754
                                offset = rseg->last_offset;
 
755
                        }
 
756
                }
 
757
 
 
758
                mutex_exit(&(rseg->mutex));
 
759
 
 
760
                rseg = UT_LIST_GET_NEXT(rseg_list, rseg);
 
761
        }
 
762
 
 
763
        if (min_rseg == NULL) {
 
764
 
 
765
                return;
 
766
        }
 
767
 
 
768
        mtr_start(&mtr);
 
769
 
 
770
        if (!min_rseg->last_del_marks) {
 
771
                /* No need to purge this log */
 
772
 
 
773
                rec = &trx_purge_dummy_rec;
 
774
        } else {
 
775
                rec = trx_undo_get_first_rec(space, zip_size, page_no, offset,
 
776
                                             RW_S_LATCH, &mtr);
 
777
                if (rec == NULL) {
 
778
                        /* Undo log empty */
 
779
 
 
780
                        rec = &trx_purge_dummy_rec;
 
781
                }
 
782
        }
 
783
 
 
784
        purge_sys->next_stored = TRUE;
 
785
        purge_sys->rseg = min_rseg;
 
786
 
 
787
        purge_sys->hdr_page_no = page_no;
 
788
        purge_sys->hdr_offset = offset;
 
789
 
 
790
        purge_sys->purge_trx_no = min_trx_no;
 
791
 
 
792
        if (rec == &trx_purge_dummy_rec) {
 
793
 
 
794
                purge_sys->purge_undo_no = ut_dulint_zero;
 
795
                purge_sys->page_no = page_no;
 
796
                purge_sys->offset = 0;
 
797
        } else {
 
798
                purge_sys->purge_undo_no = trx_undo_rec_get_undo_no(rec);
 
799
 
 
800
                purge_sys->page_no = page_get_page_no(page_align(rec));
 
801
                purge_sys->offset = page_offset(rec);
 
802
        }
 
803
 
 
804
        mtr_commit(&mtr);
 
805
}
 
806
 
 
807
/***************************************************************************
 
808
Gets the next record to purge and updates the info in the purge system. */
 
809
static
 
810
trx_undo_rec_t*
 
811
trx_purge_get_next_rec(
 
812
/*===================*/
 
813
                                /* out: copy of an undo log record or
 
814
                                pointer to the dummy undo log record */
 
815
        mem_heap_t*     heap)   /* in: memory heap where copied */
 
816
{
 
817
        trx_undo_rec_t* rec;
 
818
        trx_undo_rec_t* rec_copy;
 
819
        trx_undo_rec_t* rec2;
 
820
        trx_undo_rec_t* next_rec;
 
821
        page_t*         undo_page;
 
822
        page_t*         page;
 
823
        ulint           offset;
 
824
        ulint           page_no;
 
825
        ulint           space;
 
826
        ulint           zip_size;
 
827
        ulint           type;
 
828
        ulint           cmpl_info;
 
829
        mtr_t           mtr;
 
830
 
 
831
        ut_ad(mutex_own(&(purge_sys->mutex)));
 
832
        ut_ad(purge_sys->next_stored);
 
833
 
 
834
        space = purge_sys->rseg->space;
 
835
        zip_size = purge_sys->rseg->zip_size;
 
836
        page_no = purge_sys->page_no;
 
837
        offset = purge_sys->offset;
 
838
 
 
839
        if (offset == 0) {
 
840
                /* It is the dummy undo log record, which means that there is
 
841
                no need to purge this undo log */
 
842
 
 
843
                trx_purge_rseg_get_next_history_log(purge_sys->rseg);
 
844
 
 
845
                /* Look for the next undo log and record to purge */
 
846
 
 
847
                trx_purge_choose_next_log();
 
848
 
 
849
                return(&trx_purge_dummy_rec);
 
850
        }
 
851
 
 
852
        mtr_start(&mtr);
 
853
 
 
854
        undo_page = trx_undo_page_get_s_latched(space, zip_size,
 
855
                                                page_no, &mtr);
 
856
        rec = undo_page + offset;
 
857
 
 
858
        rec2 = rec;
 
859
 
 
860
        for (;;) {
 
861
                /* Try first to find the next record which requires a purge
 
862
                operation from the same page of the same undo log */
 
863
 
 
864
                next_rec = trx_undo_page_get_next_rec(rec2,
 
865
                                                      purge_sys->hdr_page_no,
 
866
                                                      purge_sys->hdr_offset);
 
867
                if (next_rec == NULL) {
 
868
                        rec2 = trx_undo_get_next_rec(
 
869
                                rec2, purge_sys->hdr_page_no,
 
870
                                purge_sys->hdr_offset, &mtr);
 
871
                        break;
 
872
                }
 
873
 
 
874
                rec2 = next_rec;
 
875
 
 
876
                type = trx_undo_rec_get_type(rec2);
 
877
 
 
878
                if (type == TRX_UNDO_DEL_MARK_REC) {
 
879
 
 
880
                        break;
 
881
                }
 
882
 
 
883
                cmpl_info = trx_undo_rec_get_cmpl_info(rec2);
 
884
 
 
885
                if (trx_undo_rec_get_extern_storage(rec2)) {
 
886
                        break;
 
887
                }
 
888
 
 
889
                if ((type == TRX_UNDO_UPD_EXIST_REC)
 
890
                    && !(cmpl_info & UPD_NODE_NO_ORD_CHANGE)) {
 
891
                        break;
 
892
                }
 
893
        }
 
894
 
 
895
        if (rec2 == NULL) {
 
896
                mtr_commit(&mtr);
 
897
 
 
898
                trx_purge_rseg_get_next_history_log(purge_sys->rseg);
 
899
 
 
900
                /* Look for the next undo log and record to purge */
 
901
 
 
902
                trx_purge_choose_next_log();
 
903
 
 
904
                mtr_start(&mtr);
 
905
 
 
906
                undo_page = trx_undo_page_get_s_latched(space, zip_size,
 
907
                                                        page_no, &mtr);
 
908
 
 
909
                rec = undo_page + offset;
 
910
        } else {
 
911
                page = page_align(rec2);
 
912
 
 
913
                purge_sys->purge_undo_no = trx_undo_rec_get_undo_no(rec2);
 
914
                purge_sys->page_no = page_get_page_no(page);
 
915
                purge_sys->offset = rec2 - page;
 
916
 
 
917
                if (undo_page != page) {
 
918
                        /* We advance to a new page of the undo log: */
 
919
                        purge_sys->n_pages_handled++;
 
920
                }
 
921
        }
 
922
 
 
923
        rec_copy = trx_undo_rec_copy(rec, heap);
 
924
 
 
925
        mtr_commit(&mtr);
 
926
 
 
927
        return(rec_copy);
 
928
}
 
929
 
 
930
/************************************************************************
 
931
Fetches the next undo log record from the history list to purge. It must be
 
932
released with the corresponding release function. */
 
933
UNIV_INTERN
 
934
trx_undo_rec_t*
 
935
trx_purge_fetch_next_rec(
 
936
/*=====================*/
 
937
                                /* out: copy of an undo log record or
 
938
                                pointer to the dummy undo log record
 
939
                                &trx_purge_dummy_rec, if the whole undo log
 
940
                                can skipped in purge; NULL if none left */
 
941
        dulint*         roll_ptr,/* out: roll pointer to undo record */
 
942
        trx_undo_inf_t** cell,  /* out: storage cell for the record in the
 
943
                                purge array */
 
944
        mem_heap_t*     heap)   /* in: memory heap where copied */
 
945
{
 
946
        trx_undo_rec_t* undo_rec;
 
947
 
 
948
        mutex_enter(&(purge_sys->mutex));
 
949
 
 
950
        if (purge_sys->state == TRX_STOP_PURGE) {
 
951
                trx_purge_truncate_if_arr_empty();
 
952
 
 
953
                mutex_exit(&(purge_sys->mutex));
 
954
 
 
955
                return(NULL);
 
956
        }
 
957
 
 
958
        if (!purge_sys->next_stored) {
 
959
                trx_purge_choose_next_log();
 
960
 
 
961
                if (!purge_sys->next_stored) {
 
962
                        purge_sys->state = TRX_STOP_PURGE;
 
963
 
 
964
                        trx_purge_truncate_if_arr_empty();
 
965
 
 
966
                        if (srv_print_thread_releases) {
 
967
                                fprintf(stderr,
 
968
                                        "Purge: No logs left in the"
 
969
                                        " history list; pages handled %lu\n",
 
970
                                        (ulong) purge_sys->n_pages_handled);
 
971
                        }
 
972
 
 
973
                        mutex_exit(&(purge_sys->mutex));
 
974
 
 
975
                        return(NULL);
 
976
                }
 
977
        }
 
978
 
 
979
        if (purge_sys->n_pages_handled >= purge_sys->handle_limit) {
 
980
 
 
981
                purge_sys->state = TRX_STOP_PURGE;
 
982
 
 
983
                trx_purge_truncate_if_arr_empty();
 
984
 
 
985
                mutex_exit(&(purge_sys->mutex));
 
986
 
 
987
                return(NULL);
 
988
        }
 
989
 
 
990
        if (ut_dulint_cmp(purge_sys->purge_trx_no,
 
991
                          purge_sys->view->low_limit_no) >= 0) {
 
992
                purge_sys->state = TRX_STOP_PURGE;
 
993
 
 
994
                trx_purge_truncate_if_arr_empty();
 
995
 
 
996
                mutex_exit(&(purge_sys->mutex));
 
997
 
 
998
                return(NULL);
 
999
        }
 
1000
 
 
1001
        /*      fprintf(stderr, "Thread %lu purging trx %lu undo record %lu\n",
 
1002
        os_thread_get_curr_id(),
 
1003
        ut_dulint_get_low(purge_sys->purge_trx_no),
 
1004
        ut_dulint_get_low(purge_sys->purge_undo_no)); */
 
1005
 
 
1006
        *roll_ptr = trx_undo_build_roll_ptr(FALSE, (purge_sys->rseg)->id,
 
1007
                                            purge_sys->page_no,
 
1008
                                            purge_sys->offset);
 
1009
 
 
1010
        *cell = trx_purge_arr_store_info(purge_sys->purge_trx_no,
 
1011
                                         purge_sys->purge_undo_no);
 
1012
 
 
1013
        ut_ad(ut_dulint_cmp(purge_sys->purge_trx_no,
 
1014
                            (purge_sys->view)->low_limit_no) < 0);
 
1015
 
 
1016
        /* The following call will advance the stored values of purge_trx_no
 
1017
        and purge_undo_no, therefore we had to store them first */
 
1018
 
 
1019
        undo_rec = trx_purge_get_next_rec(heap);
 
1020
 
 
1021
        mutex_exit(&(purge_sys->mutex));
 
1022
 
 
1023
        return(undo_rec);
 
1024
}
 
1025
 
 
1026
/***********************************************************************
 
1027
Releases a reserved purge undo record. */
 
1028
UNIV_INTERN
 
1029
void
 
1030
trx_purge_rec_release(
 
1031
/*==================*/
 
1032
        trx_undo_inf_t* cell)   /* in: storage cell */
 
1033
{
 
1034
        trx_undo_arr_t* arr;
 
1035
 
 
1036
        mutex_enter(&(purge_sys->mutex));
 
1037
 
 
1038
        arr = purge_sys->arr;
 
1039
 
 
1040
        trx_purge_arr_remove_info(cell);
 
1041
 
 
1042
        mutex_exit(&(purge_sys->mutex));
 
1043
}
 
1044
 
 
1045
/***********************************************************************
 
1046
This function runs a purge batch. */
 
1047
UNIV_INTERN
 
1048
ulint
 
1049
trx_purge(void)
 
1050
/*===========*/
 
1051
                                /* out: number of undo log pages handled in
 
1052
                                the batch */
 
1053
{
 
1054
        que_thr_t*      thr;
 
1055
        /*      que_thr_t*      thr2; */
 
1056
        ulint           old_pages_handled;
 
1057
 
 
1058
        mutex_enter(&(purge_sys->mutex));
 
1059
 
 
1060
        if (purge_sys->trx->n_active_thrs > 0) {
 
1061
 
 
1062
                mutex_exit(&(purge_sys->mutex));
 
1063
 
 
1064
                /* Should not happen */
 
1065
 
 
1066
                ut_error;
 
1067
 
 
1068
                return(0);
 
1069
        }
 
1070
 
 
1071
        rw_lock_x_lock(&(purge_sys->latch));
 
1072
 
 
1073
        mutex_enter(&kernel_mutex);
 
1074
 
 
1075
        /* Close and free the old purge view */
 
1076
 
 
1077
        read_view_close(purge_sys->view);
 
1078
        purge_sys->view = NULL;
 
1079
        mem_heap_empty(purge_sys->heap);
 
1080
 
 
1081
        /* Determine how much data manipulation language (DML) statements
 
1082
        need to be delayed in order to reduce the lagging of the purge
 
1083
        thread. */
 
1084
        srv_dml_needed_delay = 0; /* in microseconds; default: no delay */
 
1085
 
 
1086
        /* If we cannot advance the 'purge view' because of an old
 
1087
        'consistent read view', then the DML statements cannot be delayed.
 
1088
        Also, srv_max_purge_lag <= 0 means 'infinity'. */
 
1089
        if (srv_max_purge_lag > 0
 
1090
            && !UT_LIST_GET_LAST(trx_sys->view_list)) {
 
1091
                float   ratio = (float) trx_sys->rseg_history_len
 
1092
                        / srv_max_purge_lag;
 
1093
                if (ratio > ULINT_MAX / 10000) {
 
1094
                        /* Avoid overflow: maximum delay is 4295 seconds */
 
1095
                        srv_dml_needed_delay = ULINT_MAX;
 
1096
                } else if (ratio > 1) {
 
1097
                        /* If the history list length exceeds the
 
1098
                        innodb_max_purge_lag, the
 
1099
                        data manipulation statements are delayed
 
1100
                        by at least 5000 microseconds. */
 
1101
                        srv_dml_needed_delay = (ulint) ((ratio - .5) * 10000);
 
1102
                }
 
1103
        }
 
1104
 
 
1105
        purge_sys->view = read_view_oldest_copy_or_open_new(ut_dulint_zero,
 
1106
                                                            purge_sys->heap);
 
1107
        mutex_exit(&kernel_mutex);
 
1108
 
 
1109
        rw_lock_x_unlock(&(purge_sys->latch));
 
1110
 
 
1111
        purge_sys->state = TRX_PURGE_ON;
 
1112
 
 
1113
        /* Handle at most 20 undo log pages in one purge batch */
 
1114
 
 
1115
        purge_sys->handle_limit = purge_sys->n_pages_handled + 20;
 
1116
 
 
1117
        old_pages_handled = purge_sys->n_pages_handled;
 
1118
 
 
1119
        mutex_exit(&(purge_sys->mutex));
 
1120
 
 
1121
        mutex_enter(&kernel_mutex);
 
1122
 
 
1123
        thr = que_fork_start_command(purge_sys->query);
 
1124
 
 
1125
        ut_ad(thr);
 
1126
 
 
1127
        /*      thr2 = que_fork_start_command(purge_sys->query);
 
1128
 
 
1129
        ut_ad(thr2); */
 
1130
 
 
1131
 
 
1132
        mutex_exit(&kernel_mutex);
 
1133
 
 
1134
        /*      srv_que_task_enqueue(thr2); */
 
1135
 
 
1136
        if (srv_print_thread_releases) {
 
1137
 
 
1138
                fputs("Starting purge\n", stderr);
 
1139
        }
 
1140
 
 
1141
        que_run_threads(thr);
 
1142
 
 
1143
        if (srv_print_thread_releases) {
 
1144
 
 
1145
                fprintf(stderr,
 
1146
                        "Purge ends; pages handled %lu\n",
 
1147
                        (ulong) purge_sys->n_pages_handled);
 
1148
        }
 
1149
 
 
1150
        return(purge_sys->n_pages_handled - old_pages_handled);
 
1151
}
 
1152
 
 
1153
/**********************************************************************
 
1154
Prints information of the purge system to stderr. */
 
1155
UNIV_INTERN
 
1156
void
 
1157
trx_purge_sys_print(void)
 
1158
/*=====================*/
 
1159
{
 
1160
        fprintf(stderr, "InnoDB: Purge system view:\n");
 
1161
        read_view_print(purge_sys->view);
 
1162
 
 
1163
        fprintf(stderr, "InnoDB: Purge trx n:o " TRX_ID_FMT
 
1164
                ", undo n:o " TRX_ID_FMT "\n",
 
1165
                TRX_ID_PREP_PRINTF(purge_sys->purge_trx_no),
 
1166
                TRX_ID_PREP_PRINTF(purge_sys->purge_undo_no));
 
1167
        fprintf(stderr,
 
1168
                "InnoDB: Purge next stored %lu, page_no %lu, offset %lu,\n"
 
1169
                "InnoDB: Purge hdr_page_no %lu, hdr_offset %lu\n",
 
1170
                (ulong) purge_sys->next_stored,
 
1171
                (ulong) purge_sys->page_no,
 
1172
                (ulong) purge_sys->offset,
 
1173
                (ulong) purge_sys->hdr_page_no,
 
1174
                (ulong) purge_sys->hdr_offset);
 
1175
}