Files
orbit/internal/storage/mysql/keyed_concurrency.go

72 lines
2.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package mysql
import (
"context"
"crypto/sha256"
"database/sql"
"errors"
"git.yuntuo.hk/k2safe/orbit/internal/fault"
core "git.yuntuo.hk/k2safe/orbit/model"
)
func concurrencyDigest(key string) *core.Digest {
if key == "" {
return nil
}
digest := core.Digest(sha256.Sum256(append([]byte("orbit-concurrency-key-v1\x00"), []byte(key)...)))
return &digest
}
// concurrencyKeyValue 在 Go 中判断空字节串,避免 MySQL 文本排序规则将某些字符视为空。
func concurrencyKeyValue(key string) *string {
if key == "" {
return nil
}
return &key
}
// keyEligibility 在 LIMIT 前排除已占用资源,避免前 16 个同键任务挡住其他资源。
// 调用者已锁应用额度与队列;命名空间中的所有领取都遵守此顺序。
const keyEligibility = `(concurrency_key_digest IS NULL OR NOT EXISTS (
SELECT 1 FROM pulse_concurrency_limits k WHERE k.namespace_id=pulse_tasks.namespace_id
AND k.scope_key='key' AND k.key_digest=pulse_tasks.concurrency_key_digest AND k.used_permits>=k.max_permits))`
// reserveConcurrencyKey 为一个显式资源键预占单个许可。零 token 表示未启用或暂不可用。
// 序号使用资源行单调 revision;释放也递增,序号允许有间隔,禁止删除或重置资源行。
func reserveConcurrencyKey(ctx context.Context, tx *sql.Tx, ns core.ID, key string) (core.ID, uint64, error) {
if key == "" {
return "", 0, nil
}
id, err := core.NewID()
if err != nil {
return "", 0, err
}
digest := concurrencyDigest(key)
_, err = tx.ExecContext(ctx, `INSERT INTO pulse_concurrency_limits(namespace_id,id,scope_key,key_digest,max_permits) VALUES (?,?,'key',?,1) ON DUPLICATE KEY UPDATE id=pulse_concurrency_limits.id`, ns, id, digest)
if err != nil {
return "", 0, err
}
var used, max uint32
var revision uint64
err = tx.QueryRowContext(ctx, `SELECT id,used_permits,max_permits,revision FROM pulse_concurrency_limits WHERE namespace_id=? AND scope_key='key' AND key_digest=? FOR UPDATE`, ns, digest).Scan(&id, &used, &max, &revision)
if err != nil {
return "", 0, err
}
if max != 1 {
return "", 0, errors.New("key permit limit must be one")
}
if used != 0 {
return id, 0, nil
}
// 为当前授权及后续释放各保留一次递增空间,不能溢出后重用旧序号。
if revision > ^uint64(0)-2 {
return "", 0, fault.Conflict
}
r, err := tx.ExecContext(ctx, `UPDATE pulse_concurrency_limits SET used_permits=1,revision=revision+1,updated_at=UTC_TIMESTAMP(6) WHERE namespace_id=? AND id=? AND used_permits=0 AND revision=?`, ns, id, revision)
if err := exactlyOne(r, err); err != nil {
return "", 0, err
}
return id, revision + 1, nil
}