diff options
author | Darrick J. Wong <darrick.wong@oracle.com> | 2020-02-19 17:02:26 -0800 |
---|---|---|
committer | Darrick J. Wong <darrick.wong@oracle.com> | 2020-06-01 21:16:39 -0700 |
commit | 4ec2c701e376753c8a81a658f89857938c977601 (patch) | |
tree | 1e6a5e00402ba442550b6c9cd50121b73bce3b3c | |
parent | cef4b49366bbeb87c6f271a212d780aaff419c48 (diff) |
xfs: enable bigtime for quota timers
Enable the bigtime feature for quota timers. We decrease the accuracy
of the timers to ~4s in exchange for being able to set timers up to the
bigtime maximum.
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
-rw-r--r-- | fs/xfs/libxfs/xfs_dquot_buf.c | 72 | ||||
-rw-r--r-- | fs/xfs/libxfs/xfs_format.h | 22 | ||||
-rw-r--r-- | fs/xfs/libxfs/xfs_quota_defs.h | 11 | ||||
-rw-r--r-- | fs/xfs/scrub/quota.c | 5 | ||||
-rw-r--r-- | fs/xfs/xfs_dquot.c | 69 | ||||
-rw-r--r-- | fs/xfs/xfs_ondisk.h | 7 | ||||
-rw-r--r-- | fs/xfs/xfs_qm.c | 13 | ||||
-rw-r--r-- | fs/xfs/xfs_qm.h | 2 | ||||
-rw-r--r-- | fs/xfs/xfs_qm_syscalls.c | 17 |
9 files changed, 182 insertions, 36 deletions
diff --git a/fs/xfs/libxfs/xfs_dquot_buf.c b/fs/xfs/libxfs/xfs_dquot_buf.c index 72e0fcfef580..2b5d51a6d64b 100644 --- a/fs/xfs/libxfs/xfs_dquot_buf.c +++ b/fs/xfs/libxfs/xfs_dquot_buf.c @@ -40,6 +40,8 @@ xfs_dquot_verify( xfs_dqid_t id, uint type) /* used only during quotacheck */ { + uint8_t dtype; + /* * We can encounter an uninitialized dquot buffer for 2 reasons: * 1. If we crash while deleting the quotainode(s), and those blks got @@ -60,11 +62,22 @@ xfs_dquot_verify( if (ddq->d_version != XFS_DQUOT_VERSION) return __this_address; - if (type && ddq->d_flags != type) + dtype = ddq->d_flags & XFS_DQ_ALLTYPES; + if (type && dtype != type) + return __this_address; + if (dtype != XFS_DQ_USER && + dtype != XFS_DQ_PROJ && + dtype != XFS_DQ_GROUP) + return __this_address; + + if (ddq->d_flags & ~(XFS_DQ_ALLTYPES | XFS_DQ_BIGTIME)) return __this_address; - if (ddq->d_flags != XFS_DQ_USER && - ddq->d_flags != XFS_DQ_PROJ && - ddq->d_flags != XFS_DQ_GROUP) + + if ((ddq->d_flags & XFS_DQ_BIGTIME) && + !xfs_sb_version_hasbigtime(&mp->m_sb)) + return __this_address; + + if ((ddq->d_flags & XFS_DQ_BIGTIME) && !ddq->d_id) return __this_address; if (id != -1 && id != be32_to_cpu(ddq->d_id)) @@ -290,17 +303,68 @@ const struct xfs_buf_ops xfs_dquot_buf_ra_ops = { void xfs_dquot_from_disk_timestamp( + struct xfs_disk_dquot *ddq, struct timespec64 *tv, __be32 dtimer) { tv->tv_nsec = 0; + + /* Zero always means zero, regardless of encoding. */ + if (!dtimer) { + tv->tv_sec = 0; + return; + } + + if (ddq->d_flags & XFS_DQ_BIGTIME) { + uint64_t t; + + t = be32_to_cpu(dtimer); + tv->tv_sec = t << XFS_DQ_BIGTIME_SHIFT; + return; + } + tv->tv_sec = be32_to_cpu(dtimer); } void xfs_dquot_to_disk_timestamp( + struct xfs_disk_dquot *ddq, __be32 *dtimer, const struct timespec64 *tv) { + /* Zero always means zero, regardless of encoding. */ + if ((ddq->d_flags & XFS_DQ_BIGTIME) && tv->tv_sec != 0) { + uint64_t t = tv->tv_sec; + + /* + * Round the end of the grace period up to the nearest bigtime + * interval that we support, to give users the most time to fix + * the problems. + */ + t = roundup_64(t, 1U << XFS_DQ_BIGTIME_SHIFT); + *dtimer = cpu_to_be32(t >> XFS_DQ_BIGTIME_SHIFT); + return; + } + *dtimer = cpu_to_be32(tv->tv_sec); } + +/* + * Convert the dquot to bigtime format incore so that we'll write out the new + * values the next time we flush the dquot to disk. Skip this for d_id == 0 + * because that dquot stores the grace period intervals. Returns true if we + * upgraded the format, false otherwise. + */ +bool +xfs_dquot_add_bigtime( + struct xfs_mount *mp, + struct xfs_disk_dquot *ddq) +{ + if (xfs_sb_version_hasbigtime(&mp->m_sb) && ddq->d_id && + !(ddq->d_flags & XFS_DQ_BIGTIME)) { + ddq->d_flags |= XFS_DQ_BIGTIME; + return true; + } + + return false; +} diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h index 159182978ebc..932984737e07 100644 --- a/fs/xfs/libxfs/xfs_format.h +++ b/fs/xfs/libxfs/xfs_format.h @@ -1262,6 +1262,28 @@ static inline void xfs_dinode_put_rdev(struct xfs_dinode *dip, xfs_dev_t rdev) #define XFS_DQ_GRACE_MAX ((int64_t)U32_MAX) /* + * When bigtime is enabled, we trade a few bits of precision to expand the + * expiration timeout range to match that of big inode timestamps. The grace + * periods stored in dquot 0 are not shifted, since they record an interval, + * not a timestamp. + */ +#define XFS_DQ_BIGTIME_SHIFT (2) + +/* + * Smallest possible quota expiration with big timestamps, which is + * Jan 1 00:00:01 UTC 1970. + */ +#define XFS_DQ_BIGTIMEOUT_MIN (XFS_DQ_TIMEOUT_MIN) + +/* + * Largest supported quota expiration with traditional timestamps, which is + * the largest bigtime inode timestamp, or Jul 2 20:20:25 UTC 2486. The field + * is large enough that it's possible to fit expirations up to 2514, but we + * want to keep the maximum timestamp in sync. + */ +#define XFS_DQ_BIGTIMEOUT_MAX (XFS_INO_BIGTIME_MAX) + +/* * This is the main portion of the on-disk representation of quota * information for a user. This is the q_core of the struct xfs_dquot that * is kept in kernel memory. We pad this with some more expansion room diff --git a/fs/xfs/libxfs/xfs_quota_defs.h b/fs/xfs/libxfs/xfs_quota_defs.h index c453611ade3b..4ac486fd20a8 100644 --- a/fs/xfs/libxfs/xfs_quota_defs.h +++ b/fs/xfs/libxfs/xfs_quota_defs.h @@ -26,6 +26,7 @@ typedef uint16_t xfs_qwarncnt_t; #define XFS_DQ_GROUP 0x0004 /* a group quota */ #define XFS_DQ_DIRTY 0x0008 /* dquot is dirty */ #define XFS_DQ_FREEING 0x0010 /* dquot is being torn down */ +#define XFS_DQ_BIGTIME 0x0020 /* big timestamp format */ #define XFS_DQ_ALLTYPES (XFS_DQ_USER|XFS_DQ_PROJ|XFS_DQ_GROUP) @@ -34,7 +35,8 @@ typedef uint16_t xfs_qwarncnt_t; { XFS_DQ_PROJ, "PROJ" }, \ { XFS_DQ_GROUP, "GROUP" }, \ { XFS_DQ_DIRTY, "DIRTY" }, \ - { XFS_DQ_FREEING, "FREEING" } + { XFS_DQ_FREEING, "FREEING" }, \ + { XFS_DQ_BIGTIME, "BIGTIME" } /* * We have the possibility of all three quota types being active at once, and @@ -144,7 +146,10 @@ extern xfs_failaddr_t xfs_dqblk_verify(struct xfs_mount *mp, extern int xfs_calc_dquots_per_chunk(unsigned int nbblks); extern void xfs_dqblk_repair(struct xfs_mount *mp, struct xfs_dqblk *dqb, xfs_dqid_t id, uint type); -void xfs_dquot_from_disk_timestamp(struct timespec64 *tv, __be32 dtimer); -void xfs_dquot_to_disk_timestamp(__be32 *dtimer, const struct timespec64 *tv); +void xfs_dquot_from_disk_timestamp(struct xfs_disk_dquot *ddq, + struct timespec64 *tv, __be32 dtimer); +void xfs_dquot_to_disk_timestamp(struct xfs_disk_dquot *ddq, + __be32 *dtimer, const struct timespec64 *tv); +bool xfs_dquot_add_bigtime(struct xfs_mount *mp, struct xfs_disk_dquot *ddq); #endif /* __XFS_QUOTA_H__ */ diff --git a/fs/xfs/scrub/quota.c b/fs/xfs/scrub/quota.c index 64e24fe5dcb2..6ff402bc0a3e 100644 --- a/fs/xfs/scrub/quota.c +++ b/fs/xfs/scrub/quota.c @@ -135,6 +135,11 @@ xchk_quota_item( if (d->d_pad0 != cpu_to_be32(0) || d->d_pad != cpu_to_be16(0)) xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset); + /* incore should always have bigtime iflag set except for root */ + if (xfs_sb_version_hasbigtime(&mp->m_sb) && d->d_id && + !(d->d_flags & XFS_DQ_BIGTIME)) + xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset); + /* Check the limits. */ bhard = be64_to_cpu(d->d_blk_hardlimit); ihard = be64_to_cpu(d->d_ino_hardlimit); diff --git a/fs/xfs/xfs_dquot.c b/fs/xfs/xfs_dquot.c index 78fb08ea461b..dae7c327eab3 100644 --- a/fs/xfs/xfs_dquot.c +++ b/fs/xfs/xfs_dquot.c @@ -117,35 +117,42 @@ xfs_quota_exceeded( */ static inline time64_t xfs_dquot_clamp_timer( - time64_t timer) + struct xfs_disk_dquot *ddq, + time64_t timer) { + if (ddq->d_flags & XFS_DQ_BIGTIME) + return clamp_t(time64_t, timer, XFS_DQ_BIGTIMEOUT_MIN, + XFS_DQ_BIGTIMEOUT_MAX); return clamp_t(time64_t, timer, XFS_DQ_TIMEOUT_MIN, XFS_DQ_TIMEOUT_MAX); } /* Set a quota grace period expiration timer. */ static inline void xfs_quota_set_timer( + struct xfs_disk_dquot *ddq, time64_t *itimer, __be32 *dtimer, time64_t limit) { struct timespec64 tv = { 0 }; - tv.tv_sec = xfs_dquot_clamp_timer(ktime_get_real_seconds() + limit); + tv.tv_sec = xfs_dquot_clamp_timer(ddq, + ktime_get_real_seconds() + limit); *itimer = tv.tv_sec; - xfs_dquot_to_disk_timestamp(dtimer, &tv); + xfs_dquot_to_disk_timestamp(ddq, dtimer, &tv); } /* Clear a quota grace period expiration timer. */ static inline void xfs_quota_clear_timer( + struct xfs_disk_dquot *ddq, time64_t *itimer, __be32 *dtimer) { struct timespec64 tv = { 0 }; *itimer = tv.tv_sec; - xfs_dquot_to_disk_timestamp(dtimer, &tv); + xfs_dquot_to_disk_timestamp(ddq, dtimer, &tv); } /* @@ -187,14 +194,14 @@ xfs_qm_adjust_dqtimers( d->d_blk_softlimit, d->d_blk_hardlimit); if (!dqp->q_btimer) { if (over) { - xfs_quota_set_timer(&dqp->q_btimer, &d->d_btimer, + xfs_quota_set_timer(d, &dqp->q_btimer, &d->d_btimer, mp->m_quotainfo->qi_btimelimit); } else { d->d_bwarns = 0; } } else { if (!over) { - xfs_quota_clear_timer(&dqp->q_btimer, &d->d_btimer); + xfs_quota_clear_timer(d, &dqp->q_btimer, &d->d_btimer); } } @@ -202,14 +209,14 @@ xfs_qm_adjust_dqtimers( d->d_ino_softlimit, d->d_ino_hardlimit); if (!dqp->q_itimer) { if (over) { - xfs_quota_set_timer(&dqp->q_itimer, &d->d_itimer, + xfs_quota_set_timer(d, &dqp->q_itimer, &d->d_itimer, mp->m_quotainfo->qi_itimelimit); } else { d->d_iwarns = 0; } } else { if (!over) { - xfs_quota_clear_timer(&dqp->q_itimer, &d->d_itimer); + xfs_quota_clear_timer(d, &dqp->q_itimer, &d->d_itimer); } } @@ -217,14 +224,16 @@ xfs_qm_adjust_dqtimers( d->d_rtb_softlimit, d->d_rtb_hardlimit); if (!dqp->q_rtbtimer) { if (over) { - xfs_quota_set_timer(&dqp->q_rtbtimer, &d->d_rtbtimer, + xfs_quota_set_timer(d, &dqp->q_rtbtimer, + &d->d_rtbtimer, mp->m_quotainfo->qi_rtbtimelimit); } else { d->d_rtbwarns = 0; } } else { if (!over) { - xfs_quota_clear_timer(&dqp->q_rtbtimer, &d->d_rtbtimer); + xfs_quota_clear_timer(d, &dqp->q_rtbtimer, + &d->d_rtbtimer); } } } @@ -262,6 +271,7 @@ xfs_qm_init_dquot_blk( d->dd_diskdq.d_version = XFS_DQUOT_VERSION; d->dd_diskdq.d_id = cpu_to_be32(curid); d->dd_diskdq.d_flags = type; + xfs_dquot_add_bigtime(mp, &d->dd_diskdq); if (xfs_sb_version_hascrc(&mp->m_sb)) { uuid_copy(&d->dd_uuid, &mp->m_sb.sb_meta_uuid); xfs_update_cksum((char *)d, sizeof(struct xfs_dqblk), @@ -563,9 +573,10 @@ xfs_dquot_from_disk( { struct timespec64 tv; struct xfs_disk_dquot *ddqp = bp->b_addr + dqp->q_bufoffset; + struct xfs_disk_dquot *iddq = &dqp->q_core; /* copy everything from disk dquot to the incore dquot */ - memcpy(&dqp->q_core, ddqp, sizeof(struct xfs_disk_dquot)); + memcpy(iddq, ddqp, sizeof(struct xfs_disk_dquot)); /* * Reservation counters are defined as reservation plus current usage @@ -575,13 +586,28 @@ xfs_dquot_from_disk( dqp->q_res_icount = be64_to_cpu(ddqp->d_icount); dqp->q_res_rtbcount = be64_to_cpu(ddqp->d_rtbcount); - xfs_dquot_from_disk_timestamp(&tv, ddqp->d_btimer); + xfs_dquot_from_disk_timestamp(ddqp, &tv, ddqp->d_btimer); dqp->q_btimer = tv.tv_sec; - xfs_dquot_from_disk_timestamp(&tv, ddqp->d_itimer); + xfs_dquot_from_disk_timestamp(ddqp, &tv, ddqp->d_itimer); dqp->q_itimer = tv.tv_sec; - xfs_dquot_from_disk_timestamp(&tv, ddqp->d_rtbtimer); + xfs_dquot_from_disk_timestamp(ddqp, &tv, ddqp->d_rtbtimer); dqp->q_rtbtimer = tv.tv_sec; + /* Upgrade to bigtime if possible. */ + if (xfs_dquot_add_bigtime(dqp->q_mount, iddq)) { + tv.tv_sec = xfs_dquot_clamp_timer(iddq, dqp->q_btimer); + xfs_dquot_to_disk_timestamp(iddq, &iddq->d_btimer, &tv); + dqp->q_btimer = tv.tv_sec; + + tv.tv_sec = xfs_dquot_clamp_timer(iddq, dqp->q_itimer); + xfs_dquot_to_disk_timestamp(iddq, &iddq->d_itimer, &tv); + dqp->q_itimer = tv.tv_sec; + + tv.tv_sec = xfs_dquot_clamp_timer(iddq, dqp->q_rtbtimer); + xfs_dquot_to_disk_timestamp(iddq, &iddq->d_rtbtimer, &tv); + dqp->q_rtbtimer = tv.tv_sec; + } + /* initialize the dquot speculative prealloc thresholds */ xfs_dquot_set_prealloc_limits(dqp); } @@ -1211,6 +1237,21 @@ xfs_qm_dqflush( memcpy(ddqp, &dqp->q_core, sizeof(struct xfs_disk_dquot)); /* + * We should never write non-bigtime dquots to a bigtime fs, except for + * the root dquot. + */ + if (!(dqp->q_core.d_flags & XFS_DQ_BIGTIME) && dqp->q_core.d_id && + xfs_sb_version_hasbigtime(&mp->m_sb)) { + xfs_alert(mp, "corrupt dquot ID 0x%x in memory at %pS", + be32_to_cpu(ddqp->d_id), __this_address); + xfs_buf_relse(bp); + xfs_dqfunlock(dqp); + xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_INCORE); + xfs_quota_mark_sick(mp, dqp->dq_flags); + return -EFSCORRUPTED; + } + + /* * Clear the dirty field and remember the flush lsn for later use. */ dqp->dq_flags &= ~XFS_DQ_DIRTY; diff --git a/fs/xfs/xfs_ondisk.h b/fs/xfs/xfs_ondisk.h index 80129b2dc392..734a0fe7dd73 100644 --- a/fs/xfs/xfs_ondisk.h +++ b/fs/xfs/xfs_ondisk.h @@ -32,6 +32,13 @@ xfs_check_limits(void) XFS_CHECK_VALUE(XFS_DQ_TIMEOUT_MAX, 4294967295LL); XFS_CHECK_VALUE(XFS_DQ_GRACE_MIN, 0LL); XFS_CHECK_VALUE(XFS_DQ_GRACE_MAX, 4294967295LL); + XFS_CHECK_VALUE(XFS_DQ_BIGTIMEOUT_MIN, 1LL); + XFS_CHECK_VALUE(XFS_DQ_BIGTIMEOUT_MAX, 16299260425LL); + + BUILD_BUG_ON_MSG((XFS_DQ_TIMEOUT_MAX << XFS_DQ_BIGTIME_SHIFT) < + XFS_DQ_BIGTIMEOUT_MAX, + "XFS: quota timeout field is not large enough to fit " + "XFS_DQ_BIGTIMEOUT_MAX"); } static inline void __init diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c index b040ff7e0259..3e4f65681852 100644 --- a/fs/xfs/xfs_qm.c +++ b/fs/xfs/xfs_qm.c @@ -634,15 +634,15 @@ xfs_qm_init_timelimits( * more writing. If it is zero, a default is used. */ if (ddqp->d_btimer) { - xfs_dquot_from_disk_timestamp(&tv, ddqp->d_btimer); + xfs_dquot_from_disk_timestamp(ddqp, &tv, ddqp->d_btimer); qinf->qi_btimelimit = tv.tv_sec; } if (ddqp->d_itimer) { - xfs_dquot_from_disk_timestamp(&tv, ddqp->d_itimer); + xfs_dquot_from_disk_timestamp(ddqp, &tv, ddqp->d_itimer); qinf->qi_itimelimit = tv.tv_sec; } if (ddqp->d_rtbtimer) { - xfs_dquot_from_disk_timestamp(&tv, ddqp->d_rtbtimer); + xfs_dquot_from_disk_timestamp(ddqp, &tv, ddqp->d_rtbtimer); qinf->qi_rtbtimelimit = tv.tv_sec; } if (ddqp->d_bwarns) @@ -908,12 +908,13 @@ xfs_qm_reset_dqcounts( * should not reset them. */ if (ddq->d_id != 0) { - xfs_dquot_to_disk_timestamp(&ddq->d_btimer, &tv); - xfs_dquot_to_disk_timestamp(&ddq->d_itimer, &tv); - xfs_dquot_to_disk_timestamp(&ddq->d_rtbtimer, &tv); + xfs_dquot_to_disk_timestamp(ddq, &ddq->d_btimer, &tv); + xfs_dquot_to_disk_timestamp(ddq, &ddq->d_itimer, &tv); + xfs_dquot_to_disk_timestamp(ddq, &ddq->d_rtbtimer, &tv); ddq->d_bwarns = 0; ddq->d_iwarns = 0; ddq->d_rtbwarns = 0; + xfs_dquot_add_bigtime(mp, ddq); } if (xfs_sb_version_hascrc(&mp->m_sb)) { diff --git a/fs/xfs/xfs_qm.h b/fs/xfs/xfs_qm.h index e492013f28d7..d3b69524ca18 100644 --- a/fs/xfs/xfs_qm.h +++ b/fs/xfs/xfs_qm.h @@ -84,7 +84,7 @@ xfs_dquot_tree( struct xfs_quotainfo *qi, int type) { - switch (type) { + switch (type & XFS_DQ_ALLTYPES) { case XFS_DQ_USER: return &qi->qi_uquota_tree; case XFS_DQ_GROUP: diff --git a/fs/xfs/xfs_qm_syscalls.c b/fs/xfs/xfs_qm_syscalls.c index 4963d000c137..02bb2ba9cf00 100644 --- a/fs/xfs/xfs_qm_syscalls.c +++ b/fs/xfs/xfs_qm_syscalls.c @@ -442,6 +442,7 @@ xfs_qm_scall_quotaon( /* Set a new quota grace period. */ static inline void xfs_qm_set_grace( + struct xfs_disk_dquot *ddq, time64_t *qi_limit, time64_t *itimer, __be32 *dtimer, @@ -453,7 +454,7 @@ xfs_qm_set_grace( XFS_DQ_GRACE_MAX); *qi_limit = tv.tv_sec; *itimer = tv.tv_sec; - xfs_dquot_to_disk_timestamp(dtimer, &tv); + xfs_dquot_to_disk_timestamp(ddq, dtimer, &tv); } #define XFS_QC_MASK \ @@ -586,14 +587,14 @@ xfs_qm_scall_setqlim( * for warnings. */ if (newlim->d_fieldmask & QC_SPC_TIMER) - xfs_qm_set_grace(&q->qi_btimelimit, &dqp->q_btimer, + xfs_qm_set_grace(ddq, &q->qi_btimelimit, &dqp->q_btimer, &ddq->d_btimer, newlim->d_spc_timer); if (newlim->d_fieldmask & QC_INO_TIMER) - xfs_qm_set_grace(&q->qi_itimelimit, &dqp->q_itimer, + xfs_qm_set_grace(ddq, &q->qi_itimelimit, &dqp->q_itimer, &ddq->d_itimer, newlim->d_ino_timer); if (newlim->d_fieldmask & QC_RT_SPC_TIMER) - xfs_qm_set_grace(&q->qi_rtbtimelimit, &dqp->q_rtbtimer, - &ddq->d_rtbtimer, + xfs_qm_set_grace(ddq, &q->qi_rtbtimelimit, + &dqp->q_rtbtimer, &ddq->d_rtbtimer, newlim->d_rt_spc_timer); if (newlim->d_fieldmask & QC_SPC_WARNS) q->qi_bwarnlimit = newlim->d_spc_warns; @@ -659,11 +660,11 @@ xfs_qm_scall_getquota_fill_qc( * so return zeroes in that case. */ if ((!XFS_IS_UQUOTA_ENFORCED(mp) && - dqp->q_core.d_flags == XFS_DQ_USER) || + (dqp->q_core.d_flags & XFS_DQ_ALLTYPES) == XFS_DQ_USER) || (!XFS_IS_GQUOTA_ENFORCED(mp) && - dqp->q_core.d_flags == XFS_DQ_GROUP) || + (dqp->q_core.d_flags & XFS_DQ_ALLTYPES) == XFS_DQ_GROUP) || (!XFS_IS_PQUOTA_ENFORCED(mp) && - dqp->q_core.d_flags == XFS_DQ_PROJ)) { + (dqp->q_core.d_flags & XFS_DQ_ALLTYPES) == XFS_DQ_PROJ)) { dst->d_spc_timer = 0; dst->d_ino_timer = 0; dst->d_rt_spc_timer = 0; |