snapshot of https://github.com/seaweedfs/seaweedfs.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
This commit is contained in:
711
weed/filer/filer_notify_read.go
Normal file
711
weed/filer/filer_notify_read.go
Normal file
@@ -0,0 +1,711 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/log_buffer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/wdclient"
|
||||
)
|
||||
|
||||
type LogFileEntry struct {
|
||||
TsNs int64
|
||||
FileEntry *Entry
|
||||
}
|
||||
|
||||
// logFileMayContainAfter reports whether the log file named for fileTsNs can
|
||||
// hold any entry past startTsNs. The following file's name is not the bound: a
|
||||
// file is named for the start of the window it holds, minute-truncated, and the
|
||||
// window runs up to a flush interval longer, so "12-30" can hold 12:31:20 while
|
||||
// "12-31" exists alongside it. Comparing against the next name dropped exactly
|
||||
// the spanning file a mid-window cursor needs.
|
||||
func logFileMayContainAfter(fileTsNs, startTsNs int64) bool {
|
||||
return fileTsNs+int64(time.Minute)+int64(LogFlushInterval) > startTsNs
|
||||
}
|
||||
|
||||
// persistedLogScanStart backs a read position off by one flush interval before
|
||||
// choosing which log files to open. A log file is named for the start of the
|
||||
// window it holds and a window spans up to flushInterval, so a file whose name
|
||||
// sorts before the cursor's own minute can still hold entries after it -- a
|
||||
// window sealed at 12:30:59 and ending 12:31:58 lives in "12-30". Entries are
|
||||
// filtered against the exact cursor afterwards, so widening the file scan only
|
||||
// costs a little extra reading and never re-delivers.
|
||||
func persistedLogScanStart(t time.Time) time.Time {
|
||||
return t.Add(-LogFlushInterval)
|
||||
}
|
||||
|
||||
// LastShippedLogEntryTsNsForFiler mirrors the client's reader across one
|
||||
// filer's shipped files, in ship order: a file that fails mid-read still
|
||||
// delivers its readable prefix and later files still deliver after it, so the
|
||||
// newest file with readable content answers. answeredFileTsNs names that file
|
||||
// so the caller can roll back the sent state of everything newer - refs the
|
||||
// cursor did not reach must re-ship, or a transient probe failure leaves the
|
||||
// cursor behind them for the life of the connection.
|
||||
// complete reports whether the answering file was read through to its end: a
|
||||
// prefix-limited answer means the ref's unread suffix must re-ship too, or a
|
||||
// transient mid-file failure abandons it for the life of the connection.
|
||||
func (f *Filer) LastShippedLogEntryTsNsForFiler(refs []*filer_pb.LogFileChunkRef) (tsNs int64, answeredFileTsNs int64, ok bool, complete bool) {
|
||||
for i := len(refs) - 1; i >= 0; i-- {
|
||||
if tsNs, ok, complete = f.lastShippedLogEntryTsNs(refs[i].Chunks); ok {
|
||||
return tsNs, refs[i].FileTsNs, ok, complete
|
||||
}
|
||||
}
|
||||
return 0, 0, false, false
|
||||
}
|
||||
|
||||
// lookupLogChunkFn reports whether a chunk still resolves to a live volume.
|
||||
// Swapped in tests.
|
||||
var lookupLogChunkFn = func(f *Filer, fileId string) error {
|
||||
_, err := f.MasterClient.GetLookupFileIdFunction()(context.Background(), fileId)
|
||||
return err
|
||||
}
|
||||
|
||||
// lastShippedLogEntryTsNs mirrors the client's read of one shipped file: a
|
||||
// sequential scan of exactly these chunks that ends at the first unreadable
|
||||
// one, so a marker built from it never claims content the client will not
|
||||
// apply. Unreadable includes transient failures - understating the marker
|
||||
// only re-ships, overstating loses events - and no error escapes: a probe
|
||||
// failure must never block the transition the client is waiting on.
|
||||
//
|
||||
// Readability is judged where the client reads - the volumes - not by the
|
||||
// decoded-chunk cache: a chunk this server decoded an hour ago may sit on a
|
||||
// volume that has since died, and the direct-reading client stops there no
|
||||
// matter how well the cache still remembers the bytes.
|
||||
func (f *Filer) lastShippedLogEntryTsNs(chunks []*filer_pb.FileChunk) (tsNs int64, ok bool, complete bool) {
|
||||
for _, chunk := range chunks {
|
||||
if lookupErr := lookupLogChunkFn(f, chunk.GetFileIdString()); lookupErr != nil {
|
||||
return tsNs, ok, false
|
||||
}
|
||||
entries, loadErr := f.persistedLogCache.getOrLoad(chunk.GetFileIdString(), int64(chunk.Size), func() ([]*filer_pb.LogEntry, bool, error) {
|
||||
return loadLogFileEntriesFn(f.MasterClient, chunk)
|
||||
})
|
||||
if errors.Is(loadErr, errLogChunkIncomplete) {
|
||||
// Records span chunks; the client streams such a file whole.
|
||||
if streamTsNs, streamOk, streamComplete := f.lastStreamedLogEntryTsNs(chunks); streamOk {
|
||||
return streamTsNs, true, streamComplete
|
||||
}
|
||||
return tsNs, ok, false
|
||||
}
|
||||
if loadErr != nil {
|
||||
return tsNs, ok, false // prefix ends here, like the client's reader
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
tsNs, ok = entries[len(entries)-1].TsNs, true
|
||||
}
|
||||
}
|
||||
return tsNs, ok, true
|
||||
}
|
||||
|
||||
// lastStreamedLogEntryTsNs scans the chunk list as one byte stream, ending
|
||||
// where the client's reader ends: a clean or torn tail, a missing chunk, or
|
||||
// undecodable bytes all terminate the scan with the progress made.
|
||||
func (f *Filer) lastStreamedLogEntryTsNs(chunks []*filer_pb.FileChunk) (tsNs int64, ok bool, complete bool) {
|
||||
r := newLogFileStreamReader(f.MasterClient, chunks)
|
||||
if closer, isCloser := r.(io.Closer); isCloser {
|
||||
defer closer.Close()
|
||||
}
|
||||
sizeBuf := make([]byte, 4)
|
||||
for {
|
||||
if _, readErr := io.ReadFull(r, sizeBuf); readErr != nil {
|
||||
// A clean or torn tail is where the client's read ends too; only a
|
||||
// mid-stream failure leaves an unread remainder worth re-shipping.
|
||||
ended := readErr == io.EOF || readErr == io.ErrUnexpectedEOF
|
||||
return tsNs, ok, ended
|
||||
}
|
||||
size := util.BytesToUint32(sizeBuf)
|
||||
if size > maxLogEntrySize {
|
||||
return tsNs, ok, false
|
||||
}
|
||||
data := make([]byte, size)
|
||||
if _, readErr := io.ReadFull(r, data); readErr != nil {
|
||||
return tsNs, ok, false
|
||||
}
|
||||
logEntry := &filer_pb.LogEntry{}
|
||||
if unmarshalErr := logEntry.UnmarshalVT(data); unmarshalErr != nil {
|
||||
return tsNs, ok, false
|
||||
}
|
||||
tsNs, ok = logEntry.TsNs, true
|
||||
}
|
||||
}
|
||||
|
||||
// PersistedLogScanStartTsNs is the oldest file-name timestamp a scan from t
|
||||
// can still list. File names are minute-truncated, and the collector compares
|
||||
// names at minute granularity, so the bound must truncate too: an exact-ns
|
||||
// bound sits inside the boundary file's minute and disowns a file the next
|
||||
// collection will still return.
|
||||
func PersistedLogScanStartTsNs(t time.Time) int64 {
|
||||
return persistedLogScanStart(t).Truncate(time.Minute).UnixNano()
|
||||
}
|
||||
|
||||
func (f *Filer) collectPersistedLogBuffer(startPosition log_buffer.MessagePosition, stopTsNs int64) (v *OrderedLogVisitor, err error) {
|
||||
|
||||
if stopTsNs != 0 && startPosition.Time.UnixNano() > stopTsNs {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
scanFrom := persistedLogScanStart(startPosition.Time)
|
||||
startDate := fmt.Sprintf("%04d-%02d-%02d", scanFrom.Year(), scanFrom.Month(), scanFrom.Day())
|
||||
|
||||
dayEntries, _, listDayErr := f.ListDirectoryEntries(context.Background(), SystemLogDir, startDate, true, math.MaxInt32, "", "", "")
|
||||
if listDayErr != nil {
|
||||
return nil, fmt.Errorf("fail to list log by day: %w", listDayErr)
|
||||
}
|
||||
|
||||
return NewOrderedLogVisitor(f, startPosition, stopTsNs, dayEntries)
|
||||
|
||||
}
|
||||
|
||||
// CollectLogFileRefs lists persisted log files and returns their chunk references
|
||||
// without reading any data from volume servers. The client can use the returned
|
||||
// fids to read log file data directly from volume servers in parallel.
|
||||
func (f *Filer) CollectLogFileRefs(ctx context.Context, startPosition log_buffer.MessagePosition, stopTsNs int64) (refs []*filer_pb.LogFileChunkRef, lastTsNs int64, err error) {
|
||||
if stopTsNs != 0 && startPosition.Time.UnixNano() > stopTsNs {
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
scanFrom := persistedLogScanStart(startPosition.Time)
|
||||
startDate := fmt.Sprintf("%04d-%02d-%02d", scanFrom.Year(), scanFrom.Month(), scanFrom.Day())
|
||||
startHourMinute := fmt.Sprintf("%02d-%02d", scanFrom.Hour(), scanFrom.Minute())
|
||||
var stopDate, stopHourMinute string
|
||||
if stopTsNs != 0 {
|
||||
stopTime := time.Unix(0, stopTsNs).UTC()
|
||||
stopDate = fmt.Sprintf("%04d-%02d-%02d", stopTime.Year(), stopTime.Month(), stopTime.Day())
|
||||
stopHourMinute = fmt.Sprintf("%02d-%02d", stopTime.Hour(), stopTime.Minute())
|
||||
}
|
||||
|
||||
dayEntries, _, listDayErr := f.ListDirectoryEntries(ctx, SystemLogDir, startDate, true, math.MaxInt32, "", "", "")
|
||||
if listDayErr != nil {
|
||||
return nil, 0, fmt.Errorf("fail to list log by day: %w", listDayErr)
|
||||
}
|
||||
|
||||
for _, dayEntry := range dayEntries {
|
||||
if stopDate != "" && strings.Compare(dayEntry.Name(), stopDate) > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
hourMinuteEntries, _, listErr := f.ListDirectoryEntries(ctx, util.NewFullPath(SystemLogDir, dayEntry.Name()), "", false, math.MaxInt32, "", "", "")
|
||||
if listErr != nil {
|
||||
return nil, 0, fmt.Errorf("fail to list log %s: %w", dayEntry.Name(), listErr)
|
||||
}
|
||||
|
||||
for _, hmEntry := range hourMinuteEntries {
|
||||
hourMinute := util.FileNameBase(hmEntry.Name())
|
||||
if dayEntry.Name() == startDate && strings.Compare(hourMinute, startHourMinute) < 0 {
|
||||
continue
|
||||
}
|
||||
if dayEntry.Name() == stopDate && stopHourMinute != "" && strings.Compare(hourMinute, stopHourMinute) > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
tsMinute := fmt.Sprintf("%s-%s", dayEntry.Name(), hourMinute)
|
||||
t, parseErr := time.Parse("2006-01-02-15-04", tsMinute)
|
||||
if parseErr != nil {
|
||||
glog.Errorf("failed to parse %s: %v", tsMinute, parseErr)
|
||||
continue
|
||||
}
|
||||
filerId := getFilerId(hmEntry.Name())
|
||||
if filerId == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
chunks := hmEntry.GetChunks()
|
||||
if len(chunks) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
refs = append(refs, &filer_pb.LogFileChunkRef{
|
||||
Chunks: chunks,
|
||||
FileTsNs: t.UnixNano(),
|
||||
FilerId: filerId,
|
||||
})
|
||||
lastTsNs = t.UnixNano()
|
||||
}
|
||||
}
|
||||
lastTsNs = clampLogRefsCursor(lastTsNs, startPosition.Time.UnixNano())
|
||||
return
|
||||
}
|
||||
|
||||
// clampLogRefsCursor keeps a chunk-ref read from moving the subscriber's cursor
|
||||
// backwards. Refs are named for the minute their window starts in, so the last
|
||||
// one routinely sorts before the position that was asked for -- always, now
|
||||
// that the scan reaches back a flush interval to pick up a spanning file. The
|
||||
// caller makes this value the new read position, and rewinding it would replay
|
||||
// memory from before the client's own SinceNs and re-send what the chunk reader
|
||||
// has already been handed.
|
||||
func clampLogRefsCursor(lastTsNs, startTsNs int64) int64 {
|
||||
if lastTsNs < startTsNs {
|
||||
return startTsNs
|
||||
}
|
||||
return lastTsNs
|
||||
}
|
||||
|
||||
func (f *Filer) HasPersistedLogFiles(startPosition log_buffer.MessagePosition) (bool, error) {
|
||||
startDate := fmt.Sprintf("%04d-%02d-%02d", startPosition.Time.Year(), startPosition.Time.Month(), startPosition.Time.Day())
|
||||
dayEntries, _, listDayErr := f.ListDirectoryEntries(context.Background(), SystemLogDir, startDate, true, 1, "", "", "")
|
||||
|
||||
if listDayErr != nil {
|
||||
return false, fmt.Errorf("fail to list log by day: %w", listDayErr)
|
||||
}
|
||||
if len(dayEntries) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ----------
|
||||
type LogEntryItem struct {
|
||||
Entry *filer_pb.LogEntry
|
||||
filer string
|
||||
}
|
||||
|
||||
// LogEntryItemPriorityQueue a priority queue for LogEntry
|
||||
type LogEntryItemPriorityQueue []*LogEntryItem
|
||||
|
||||
func (pq LogEntryItemPriorityQueue) Len() int { return len(pq) }
|
||||
func (pq LogEntryItemPriorityQueue) Less(i, j int) bool {
|
||||
return pq[i].Entry.TsNs < pq[j].Entry.TsNs
|
||||
}
|
||||
func (pq LogEntryItemPriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] }
|
||||
func (pq *LogEntryItemPriorityQueue) Push(x any) {
|
||||
item := x.(*LogEntryItem)
|
||||
*pq = append(*pq, item)
|
||||
}
|
||||
func (pq *LogEntryItemPriorityQueue) Pop() any {
|
||||
n := len(*pq)
|
||||
item := (*pq)[n-1]
|
||||
*pq = (*pq)[:n-1]
|
||||
return item
|
||||
}
|
||||
|
||||
// ----------
|
||||
|
||||
type OrderedLogVisitor struct {
|
||||
perFilerIteratorMap map[string]*LogFileQueueIterator
|
||||
pq *LogEntryItemPriorityQueue
|
||||
logFileEntryCollector *LogFileEntryCollector
|
||||
}
|
||||
|
||||
func NewOrderedLogVisitor(f *Filer, startPosition log_buffer.MessagePosition, stopTsNs int64, dayEntries []*Entry) (*OrderedLogVisitor, error) {
|
||||
|
||||
perFilerQueueMap := make(map[string]*LogFileQueueIterator)
|
||||
// initialize the priority queue
|
||||
pq := &LogEntryItemPriorityQueue{}
|
||||
heap.Init(pq)
|
||||
|
||||
t := &OrderedLogVisitor{
|
||||
perFilerIteratorMap: perFilerQueueMap,
|
||||
pq: pq,
|
||||
logFileEntryCollector: NewLogFileEntryCollector(f, startPosition, stopTsNs, dayEntries),
|
||||
}
|
||||
if err := t.logFileEntryCollector.collectMore(t); err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (o *OrderedLogVisitor) GetNext() (logEntry *filer_pb.LogEntry, err error) {
|
||||
if o.pq.Len() == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
item := heap.Pop(o.pq).(*LogEntryItem)
|
||||
filerId := item.filer
|
||||
|
||||
// fill the pq with the next log entry from the same filer
|
||||
it := o.perFilerIteratorMap[filerId]
|
||||
next, nextErr := it.getNext(o)
|
||||
if nextErr != nil {
|
||||
if nextErr == io.EOF {
|
||||
// do nothing since the filer has no more log entries
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to get next log entry: %w", nextErr)
|
||||
}
|
||||
} else {
|
||||
heap.Push(o.pq, &LogEntryItem{
|
||||
Entry: next,
|
||||
filer: filerId,
|
||||
})
|
||||
}
|
||||
return item.Entry, nil
|
||||
}
|
||||
|
||||
// Close releases any log file readers still open across the per-filer
|
||||
// iterators, e.g. when a subscription stops before reaching the end. Safe to
|
||||
// call more than once.
|
||||
func (o *OrderedLogVisitor) Close() {
|
||||
for _, it := range o.perFilerIteratorMap {
|
||||
it.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func getFilerId(name string) string {
|
||||
idx := strings.LastIndex(name, ".")
|
||||
if idx < 0 {
|
||||
return ""
|
||||
}
|
||||
return name[idx+1:]
|
||||
}
|
||||
|
||||
// ----------
|
||||
|
||||
type LogFileEntryCollector struct {
|
||||
f *Filer
|
||||
startTsNs int64
|
||||
stopTsNs int64
|
||||
dayEntryQueue *util.Queue[*Entry]
|
||||
startDate string
|
||||
startHourMinute string
|
||||
stopDate string
|
||||
stopHourMinute string
|
||||
}
|
||||
|
||||
func NewLogFileEntryCollector(f *Filer, startPosition log_buffer.MessagePosition, stopTsNs int64, dayEntries []*Entry) *LogFileEntryCollector {
|
||||
dayEntryQueue := util.NewQueue[*Entry]()
|
||||
for _, dayEntry := range dayEntries {
|
||||
dayEntryQueue.Enqueue(dayEntry)
|
||||
// println("enqueue day entry", dayEntry.Name())
|
||||
}
|
||||
|
||||
scanFrom := persistedLogScanStart(startPosition.Time)
|
||||
startDate := fmt.Sprintf("%04d-%02d-%02d", scanFrom.Year(), scanFrom.Month(), scanFrom.Day())
|
||||
startHourMinute := fmt.Sprintf("%02d-%02d", scanFrom.Hour(), scanFrom.Minute())
|
||||
var stopDate, stopHourMinute string
|
||||
if stopTsNs != 0 {
|
||||
stopTime := time.Unix(0, stopTsNs+24*60*60*int64(time.Second)).UTC()
|
||||
stopDate = fmt.Sprintf("%04d-%02d-%02d", stopTime.Year(), stopTime.Month(), stopTime.Day())
|
||||
stopHourMinute = fmt.Sprintf("%02d-%02d", stopTime.Hour(), stopTime.Minute())
|
||||
}
|
||||
|
||||
return &LogFileEntryCollector{
|
||||
f: f,
|
||||
startTsNs: startPosition.Time.UnixNano(),
|
||||
stopTsNs: stopTsNs,
|
||||
dayEntryQueue: dayEntryQueue,
|
||||
startDate: startDate,
|
||||
startHourMinute: startHourMinute,
|
||||
stopDate: stopDate,
|
||||
stopHourMinute: stopHourMinute,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LogFileEntryCollector) collectMore(v *OrderedLogVisitor) (err error) {
|
||||
dayEntry := c.dayEntryQueue.Dequeue()
|
||||
if dayEntry == nil {
|
||||
return io.EOF
|
||||
}
|
||||
// println("dequeue day entry", dayEntry.Name())
|
||||
if c.stopDate != "" {
|
||||
if strings.Compare(dayEntry.Name(), c.stopDate) > 0 {
|
||||
return io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
hourMinuteEntries, _, listHourMinuteErr := c.f.ListDirectoryEntries(context.Background(), util.NewFullPath(SystemLogDir, dayEntry.Name()), "", false, math.MaxInt32, "", "", "")
|
||||
if listHourMinuteErr != nil {
|
||||
return fmt.Errorf("fail to list log %s by day: %v", dayEntry.Name(), listHourMinuteErr)
|
||||
}
|
||||
freshFilerIds := make(map[string]string)
|
||||
for _, hourMinuteEntry := range hourMinuteEntries {
|
||||
// println("checking hh-mm", hourMinuteEntry.FullPath)
|
||||
hourMinute := util.FileNameBase(hourMinuteEntry.Name())
|
||||
if dayEntry.Name() == c.startDate {
|
||||
if strings.Compare(hourMinute, c.startHourMinute) < 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if dayEntry.Name() == c.stopDate {
|
||||
if strings.Compare(hourMinute, c.stopHourMinute) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
tsMinute := fmt.Sprintf("%s-%s", dayEntry.Name(), hourMinute)
|
||||
// println(" enqueue", tsMinute)
|
||||
t, parseErr := time.Parse("2006-01-02-15-04", tsMinute)
|
||||
if parseErr != nil {
|
||||
glog.Errorf("failed to parse %s: %v", tsMinute, parseErr)
|
||||
continue
|
||||
}
|
||||
filerId := getFilerId(hourMinuteEntry.Name())
|
||||
if filerId == "" {
|
||||
glog.Warningf("Invalid log file name format: %s", hourMinuteEntry.Name())
|
||||
continue // Skip files with invalid format
|
||||
}
|
||||
iter, found := v.perFilerIteratorMap[filerId]
|
||||
if !found {
|
||||
iter = newLogFileQueueIterator(c.f.MasterClient, c.f.persistedLogCache, util.NewQueue[*LogFileEntry](), c.startTsNs, c.stopTsNs)
|
||||
v.perFilerIteratorMap[filerId] = iter
|
||||
freshFilerIds[filerId] = hourMinuteEntry.Name()
|
||||
}
|
||||
iter.q.Enqueue(&LogFileEntry{
|
||||
TsNs: t.UnixNano(),
|
||||
FileEntry: hourMinuteEntry,
|
||||
})
|
||||
}
|
||||
|
||||
// fill the pq with the next log entry if it is a new filer
|
||||
for filerId, entryName := range freshFilerIds {
|
||||
iter, found := v.perFilerIteratorMap[filerId]
|
||||
if !found {
|
||||
glog.Errorf("Unexpected! failed to find iterator for filer %s", filerId)
|
||||
continue
|
||||
}
|
||||
next, nextErr := iter.getNext(v)
|
||||
if nextErr != nil {
|
||||
if nextErr == io.EOF {
|
||||
// do nothing since the filer has no more log entries
|
||||
} else {
|
||||
return fmt.Errorf("failed to get next log entry for %v: %w", entryName, nextErr)
|
||||
}
|
||||
} else {
|
||||
heap.Push(v.pq, &LogEntryItem{
|
||||
Entry: next,
|
||||
filer: filerId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------
|
||||
|
||||
type LogFileQueueIterator struct {
|
||||
q *util.Queue[*LogFileEntry]
|
||||
masterClient *wdclient.MasterClient
|
||||
cache *persistedLogCache
|
||||
startTsNs int64
|
||||
stopTsNs int64
|
||||
currentFileIterator *LogFileIterator
|
||||
}
|
||||
|
||||
func newLogFileQueueIterator(masterClient *wdclient.MasterClient, cache *persistedLogCache, q *util.Queue[*LogFileEntry], startTsNs, stopTsNs int64) *LogFileQueueIterator {
|
||||
return &LogFileQueueIterator{
|
||||
q: q,
|
||||
masterClient: masterClient,
|
||||
cache: cache,
|
||||
startTsNs: startTsNs,
|
||||
stopTsNs: stopTsNs,
|
||||
}
|
||||
}
|
||||
|
||||
// Close releases the current log file reader, if any. Safe to call more than once.
|
||||
func (iter *LogFileQueueIterator) Close() {
|
||||
if iter.currentFileIterator != nil {
|
||||
if err := iter.currentFileIterator.Close(); err != nil {
|
||||
glog.Warningf("close log file %s: %v", iter.currentFileIterator.filePath, err)
|
||||
}
|
||||
iter.currentFileIterator = nil
|
||||
}
|
||||
}
|
||||
|
||||
// getNext yields one log entry at a time from the current file, advancing to
|
||||
// the next file as each is exhausted. It returns io.EOF when done. Entries are
|
||||
// decoded one chunk at a time and shared across subscribers, so per-subscriber
|
||||
// memory stays bounded regardless of log file size.
|
||||
func (iter *LogFileQueueIterator) getNext(v *OrderedLogVisitor) (logEntry *filer_pb.LogEntry, err error) {
|
||||
for {
|
||||
if iter.currentFileIterator != nil {
|
||||
logEntry, err = iter.currentFileIterator.getNext()
|
||||
if err == nil {
|
||||
return logEntry, nil
|
||||
}
|
||||
// The current file is done (io.EOF), its volume was deleted, or it is
|
||||
// unreadable. Close it on every path so the reader is not left alive
|
||||
// until GC; only a genuine read error is propagated.
|
||||
readErr := err
|
||||
switch {
|
||||
case readErr == io.EOF:
|
||||
readErr = nil
|
||||
case isChunkNotFoundError(readErr):
|
||||
// Volume or chunk was deleted, skip the rest of this log file
|
||||
glog.Warningf("skipping rest of %s: %v", iter.currentFileIterator.filePath, readErr)
|
||||
readErr = nil
|
||||
}
|
||||
if closeErr := iter.currentFileIterator.Close(); closeErr != nil {
|
||||
glog.Warningf("close log file %s: %v", iter.currentFileIterator.filePath, closeErr)
|
||||
}
|
||||
iter.currentFileIterator = nil
|
||||
if readErr != nil {
|
||||
return nil, readErr
|
||||
}
|
||||
}
|
||||
|
||||
// advance to the next file
|
||||
if iter.q.Len() == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
t := iter.q.Dequeue()
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
// skip the file if it is after the stopTsNs
|
||||
if iter.stopTsNs != 0 && t.TsNs > iter.stopTsNs {
|
||||
return nil, io.EOF
|
||||
}
|
||||
if iter.q.Peek() == nil {
|
||||
if collectErr := v.logFileEntryCollector.collectMore(v); collectErr != nil && collectErr != io.EOF {
|
||||
return nil, collectErr
|
||||
}
|
||||
}
|
||||
if !logFileMayContainAfter(t.TsNs, iter.startTsNs) {
|
||||
continue
|
||||
}
|
||||
iter.currentFileIterator = newLogFileIterator(iter.masterClient, iter.cache, t.FileEntry, iter.startTsNs, iter.stopTsNs)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------
|
||||
|
||||
type LogFileIterator struct {
|
||||
// cached mode: each immutable chunk is decoded once and shared read-only
|
||||
// across subscribers via the persisted-log cache.
|
||||
masterClient *wdclient.MasterClient
|
||||
cache *persistedLogCache
|
||||
chunks []*filer_pb.FileChunk
|
||||
chunkIdx int
|
||||
cur []*filer_pb.LogEntry
|
||||
curPos int
|
||||
lastTsNs int64
|
||||
// streaming mode: the whole file as one byte stream, the fallback when a
|
||||
// chunk does not decode standalone (records spanning chunk boundaries).
|
||||
r io.Reader
|
||||
sizeBuf []byte
|
||||
startTsNs int64
|
||||
stopTsNs int64
|
||||
filePath string
|
||||
}
|
||||
|
||||
// swapped in tests
|
||||
var loadLogFileEntriesFn = loadLogFileEntries
|
||||
var newLogFileStreamReader = func(masterClient *wdclient.MasterClient, chunks []*filer_pb.FileChunk) io.Reader {
|
||||
return NewChunkStreamReaderFromFiler(context.Background(), masterClient, chunks)
|
||||
}
|
||||
|
||||
func newLogFileIterator(masterClient *wdclient.MasterClient, cache *persistedLogCache, fileEntry *Entry, startTsNs, stopTsNs int64) *LogFileIterator {
|
||||
iter := &LogFileIterator{
|
||||
masterClient: masterClient,
|
||||
cache: cache,
|
||||
// sort a copy, leaving the listed entry's chunk order alone
|
||||
chunks: append([]*filer_pb.FileChunk(nil), fileEntry.GetChunks()...),
|
||||
startTsNs: startTsNs,
|
||||
stopTsNs: stopTsNs,
|
||||
filePath: string(fileEntry.FullPath),
|
||||
}
|
||||
sort.SliceStable(iter.chunks, func(i, j int) bool {
|
||||
return iter.chunks[i].Offset < iter.chunks[j].Offset
|
||||
})
|
||||
if cache == nil {
|
||||
iter.startStreaming()
|
||||
}
|
||||
return iter
|
||||
}
|
||||
|
||||
// startStreaming switches to the byte-stream fallback, resuming after the last
|
||||
// yielded entry.
|
||||
func (iter *LogFileIterator) startStreaming() {
|
||||
if iter.lastTsNs > iter.startTsNs {
|
||||
iter.startTsNs = iter.lastTsNs
|
||||
}
|
||||
iter.r = newLogFileStreamReader(iter.masterClient, iter.chunks)
|
||||
iter.sizeBuf = make([]byte, 4)
|
||||
iter.cur, iter.curPos = nil, 0
|
||||
}
|
||||
|
||||
func (iter *LogFileIterator) Close() error {
|
||||
if r, ok := iter.r.(io.Closer); ok {
|
||||
return r.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getNext will return io.EOF when done
|
||||
func (iter *LogFileIterator) getNext() (logEntry *filer_pb.LogEntry, err error) {
|
||||
if iter.r == nil {
|
||||
return iter.getNextCached()
|
||||
}
|
||||
var n int
|
||||
for {
|
||||
n, err = iter.r.Read(iter.sizeBuf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != 4 {
|
||||
return nil, fmt.Errorf("size %d bytes, expected 4 bytes", n)
|
||||
}
|
||||
size := util.BytesToUint32(iter.sizeBuf)
|
||||
if size > maxLogEntrySize {
|
||||
return nil, fmt.Errorf("%s entry size %d exceeds %d", iter.filePath, size, maxLogEntrySize)
|
||||
}
|
||||
entryData := make([]byte, size)
|
||||
n, err = iter.r.Read(entryData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != int(size) {
|
||||
return nil, fmt.Errorf("entry data %d bytes, expected %d bytes", n, size)
|
||||
}
|
||||
logEntry = &filer_pb.LogEntry{}
|
||||
if err = logEntry.UnmarshalVT(entryData); err != nil {
|
||||
return
|
||||
}
|
||||
if logEntry.TsNs <= iter.startTsNs {
|
||||
continue
|
||||
}
|
||||
if iter.stopTsNs != 0 && logEntry.TsNs > iter.stopTsNs {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// getNextCached yields from shared per-chunk decoded slices, loading chunks
|
||||
// lazily and in order.
|
||||
func (iter *LogFileIterator) getNextCached() (logEntry *filer_pb.LogEntry, err error) {
|
||||
for {
|
||||
for iter.curPos < len(iter.cur) {
|
||||
logEntry = iter.cur[iter.curPos]
|
||||
iter.curPos++
|
||||
if logEntry.TsNs <= iter.startTsNs {
|
||||
continue
|
||||
}
|
||||
if iter.stopTsNs != 0 && logEntry.TsNs > iter.stopTsNs {
|
||||
return nil, io.EOF
|
||||
}
|
||||
iter.lastTsNs = logEntry.TsNs
|
||||
return logEntry, nil
|
||||
}
|
||||
if iter.chunkIdx >= len(iter.chunks) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
chunk := iter.chunks[iter.chunkIdx]
|
||||
iter.chunkIdx++
|
||||
// the flush upload time upper-bounds every record ts in the chunk; the
|
||||
// margin tolerates a wall-clock retreat between stamping and upload
|
||||
if chunk.ModifiedTsNs > 0 && chunk.ModifiedTsNs+int64(LogFlushInterval) <= iter.startTsNs {
|
||||
continue
|
||||
}
|
||||
entries, loadErr := iter.cache.getOrLoad(chunk.GetFileIdString(), int64(chunk.Size), func() ([]*filer_pb.LogEntry, bool, error) {
|
||||
return loadLogFileEntriesFn(iter.masterClient, chunk)
|
||||
})
|
||||
if loadErr != nil {
|
||||
if errors.Is(loadErr, errLogChunkIncomplete) {
|
||||
glog.V(1).Infof("log file %s chunk %s does not decode standalone, streaming the file", iter.filePath, chunk.GetFileIdString())
|
||||
iter.startStreaming()
|
||||
return iter.getNext()
|
||||
}
|
||||
return nil, loadErr
|
||||
}
|
||||
iter.cur = entries
|
||||
iter.curPos = sort.Search(len(entries), func(i int) bool { return entries[i].TsNs > iter.startTsNs })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user