bump reva and deps

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
Jörn Friedrich Dreyer
2026-01-07 17:15:23 +01:00
parent 6082e0c4df
commit 8e30e535b0
126 changed files with 8819 additions and 3109 deletions
+23 -23
View File
@@ -18,7 +18,7 @@ require (
github.com/davidbyttow/govips/v2 v2.16.0
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
github.com/gabriel-vasile/mimetype v1.4.11
github.com/gabriel-vasile/mimetype v1.4.12
github.com/ggwhite/go-masker v1.1.0
github.com/go-chi/chi/v5 v5.2.3
github.com/go-chi/render v1.0.3
@@ -33,7 +33,7 @@ require (
github.com/go-micro/plugins/v4/store/nats-js-kv v0.0.0-20240726082623-6831adfdcdc4
github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus v1.2.0
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0
github.com/go-playground/validator/v10 v10.28.0
github.com/go-playground/validator/v10 v10.30.1
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/golang/protobuf v1.5.4
github.com/google/go-cmp v0.7.0
@@ -54,7 +54,7 @@ require (
github.com/mitchellh/mapstructure v1.5.0
github.com/mna/pigeon v1.3.0
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/nats-io/nats-server/v2 v2.12.2
github.com/nats-io/nats-server/v2 v2.12.3
github.com/nats-io/nats.go v1.47.0
github.com/oklog/run v1.2.0
github.com/olekukonko/tablewriter v1.1.1
@@ -64,7 +64,7 @@ require (
github.com/open-policy-agent/opa v1.11.1
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76
github.com/opencloud-eu/reva/v2 v2.41.0
github.com/opencloud-eu/reva/v2 v2.41.1-0.20260107152322-93760b632993
github.com/opensearch-project/opensearch-go/v4 v4.6.0
github.com/orcaman/concurrent-map v1.0.0
github.com/pkg/errors v0.9.1
@@ -94,11 +94,11 @@ require (
github.com/xhit/go-simple-mail/v2 v2.16.0
go-micro.dev/v4 v4.11.0
go.etcd.io/bbolt v1.4.3
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
go.opentelemetry.io/contrib/zpages v0.63.0
go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0
go.opentelemetry.io/otel/sdk v1.39.0
go.opentelemetry.io/otel/trace v1.39.0
@@ -106,13 +106,13 @@ require (
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac
golang.org/x/image v0.34.0
golang.org/x/net v0.48.0
golang.org/x/oauth2 v0.33.0
golang.org/x/oauth2 v0.34.0
golang.org/x/sync v0.19.0
golang.org/x/term v0.38.0
golang.org/x/text v0.32.0
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217
google.golang.org/grpc v1.78.0
google.golang.org/protobuf v1.36.10
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.2
@@ -135,7 +135,7 @@ require (
github.com/ajg/form v1.5.1 // indirect
github.com/alexedwards/argon2id v1.0.0 // indirect
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 // indirect
github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op // indirect
github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
@@ -162,7 +162,7 @@ require (
github.com/bombsimon/logrusr/v3 v3.1.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/ceph/go-ceph v0.36.0 // indirect
github.com/ceph/go-ceph v0.37.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cevaris/ordered_map v0.0.0-20190319150403-3adeae072e73 // indirect
github.com/clipperhouse/displaywidth v0.3.1 // indirect
@@ -241,7 +241,7 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/gomodule/redigo v1.9.3 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.6 // indirect
github.com/google/go-tpm v0.9.7 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/renameio/v2 v2.0.1 // indirect
github.com/gookit/goutil v0.7.1 // indirect
@@ -260,7 +260,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/juliangruber/go-intersect v1.1.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.1 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/kovidgoyal/go-parallel v1.1.1 // indirect
@@ -283,7 +283,7 @@ require (
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/mattn/go-sqlite3 v1.14.33 // indirect
github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b // indirect
github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 // indirect
github.com/miekg/dns v1.1.57 // indirect
@@ -307,7 +307,7 @@ require (
github.com/mschoch/smat v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nats-io/jwt/v2 v2.8.0 // indirect
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nkeys v0.4.12 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
@@ -327,9 +327,9 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/pquerna/cachecontrol v0.2.0 // indirect
github.com/prometheus/alertmanager v0.29.0 // indirect
github.com/prometheus/alertmanager v0.30.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.1 // indirect
github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/prometheus/statsd_exporter v0.22.8 // indirect
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
@@ -378,14 +378,14 @@ require (
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/yashtewari/glob-intersection v0.2.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.etcd.io/etcd/api/v3 v3.6.6 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.6.6 // indirect
go.etcd.io/etcd/client/v3 v3.6.6 // indirect
go.etcd.io/etcd/api/v3 v3.6.7 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.6.7 // indirect
go.etcd.io/etcd/client/v3 v3.6.7 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
@@ -395,7 +395,7 @@ require (
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.39.0 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
+48 -48
View File
@@ -119,8 +119,8 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op h1:+OSa/t11TFhqfrX0EOSqQBDJ0YlpmK0rDSiB19dg9M0=
github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E=
github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op h1:Ucf+QxEKMbPogRO5guBNe5cgd9uZgfoJLOYs8WWhtjM=
github.com/antithesishq/antithesis-sdk-go v0.5.0-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
@@ -210,8 +210,8 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/ceph/go-ceph v0.36.0 h1:IDE4vEF+4fmjve+CPjD1WStgfQ+Lh6vD+9PMUI712KI=
github.com/ceph/go-ceph v0.36.0/go.mod h1:fGCbndVDLuHW7q2954d6y+tgPFOBnRLqJRe2YXyngw4=
github.com/ceph/go-ceph v0.37.0 h1:KXliBe3ZDr3/AtfY7n9d1MG7ippYNCVhMPcAgm05CFI=
github.com/ceph/go-ceph v0.37.0/go.mod h1:3y2tOlITlyuVFhy8v6PpCEfjMwKPfXJiH0/2hKZZQRE=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -358,8 +358,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gdexlab/go-render v1.0.1 h1:rxqB3vo5s4n1kF0ySmoNeSPRYkEsyHgln4jFIQY7v0U=
github.com/gdexlab/go-render v1.0.1/go.mod h1:wRi5nW2qfjiGj4mPukH4UV0IknS1cHD4VgFTmJX5JzM=
github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
@@ -456,8 +456,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
@@ -494,8 +494,8 @@ github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
@@ -575,8 +575,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/go-tika v0.3.1 h1:l+jr10hDhZjcgxFRfcQChRLo1bPXQeLFluMyvDhXTTA=
github.com/google/go-tika v0.3.1/go.mod h1:DJh5N8qxXIl85QkqmXknd+PeeRkUOTbvwyYf7ieDz6c=
github.com/google/go-tpm v0.9.6 h1:Ku42PT4LmjDu1H5C5ISWLlpI1mj+Zq7sPGKoRw2XROA=
github.com/google/go-tpm v0.9.6/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm v0.9.7 h1:u89J4tUUeDTlH8xxC3CTW7OHZjbjKoHdQ9W7gCUhtxA=
github.com/google/go-tpm v0.9.7/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -728,8 +728,8 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
@@ -834,8 +834,8 @@ github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -914,12 +914,12 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g=
github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
github.com/nats-io/nats-server/v2 v2.12.2 h1:4TEQd0Y4zvcW0IsVxjlXnRso1hBkQl3TS0BI+SxgPhE=
github.com/nats-io/nats-server/v2 v2.12.2/go.mod h1:j1AAttYeu7WnvD8HLJ+WWKNMSyxsqmZ160pNtCQRMyE=
github.com/nats-io/nats-server/v2 v2.12.3 h1:KRv+1n7lddMVgkJPQer+pt36TcO0ENxjilBmeWdjcHs=
github.com/nats-io/nats-server/v2 v2.12.3/go.mod h1:MQXjG9WjyXKz9koWzUc3jYUMKD8x3CLmTNy91IQQz3Y=
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
@@ -967,8 +967,8 @@ github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9 h1:dIft
github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9/go.mod h1:JWyDC6H+5oZRdUJUgKuaye+8Ph5hEs6HVzVoPKzWSGI=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76 h1:vD/EdfDUrv4omSFjrinT8Mvf+8D7f9g4vgQ2oiDrVUI=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
github.com/opencloud-eu/reva/v2 v2.41.0 h1:oie8+sxcA+drREXRTqm0LmfUdy/mmaa6pA6wkdF6tF4=
github.com/opencloud-eu/reva/v2 v2.41.0/go.mod h1:DGH08n2mvtsQLkt8o15FV6m51FwSJJGhjR8Ty+iIJww=
github.com/opencloud-eu/reva/v2 v2.41.1-0.20260107152322-93760b632993 h1:qWU0bKhD1wqQIq6giMTvUUbG1IlaT/lzchLDSjuedi0=
github.com/opencloud-eu/reva/v2 v2.41.1-0.20260107152322-93760b632993/go.mod h1:foXaMxugUi4TTRsK3AAXRAb/kyFd4A9k2+wNv+p+vbU=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
@@ -1020,8 +1020,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om
github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k=
github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/alertmanager v0.29.0 h1:/ET4NmAGx2Dv9kStrXIBqBgHyiSgIk4OetY+hoZRfgc=
github.com/prometheus/alertmanager v0.29.0/go.mod h1:SjI2vhrfdWg10UaRUxTz27rgdJVG3HXrhI5WFjCdBgs=
github.com/prometheus/alertmanager v0.30.0 h1:E4dnxSFXK8V2Bb8iqudlisTmaIrF3hRJSWnliG08tBM=
github.com/prometheus/alertmanager v0.30.0/go.mod h1:93PBumcTLr/gNtNtM0m7BcCffbvYP5bKuLBWiOnISaA=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
@@ -1054,8 +1054,8 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI=
github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q=
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.0.0-20170703101242-e645f4e5aaa8/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
@@ -1291,12 +1291,12 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ=
go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4=
go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs=
go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI=
go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec=
go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8=
go.etcd.io/etcd/api/v3 v3.6.7 h1:7BNJ2gQmc3DNM+9cRkv7KkGQDayElg8x3X+tFDYS+E0=
go.etcd.io/etcd/api/v3 v3.6.7/go.mod h1:xJ81TLj9hxrYYEDmXTeKURMeY3qEDN24hqe+q7KhbnI=
go.etcd.io/etcd/client/pkg/v3 v3.6.7 h1:vvzgyozz46q+TyeGBuFzVuI53/yd133CHceNb/AhBVs=
go.etcd.io/etcd/client/pkg/v3 v3.6.7/go.mod h1:2IVulJ3FZ/czIGl9T4lMF1uxzrhRahLqe+hSgy+Kh7Q=
go.etcd.io/etcd/client/v3 v3.6.7 h1:9WqA5RpIBtdMxAy1ukXLAdtg2pAxNqW5NUoO2wQrE6U=
go.etcd.io/etcd/client/v3 v3.6.7/go.mod h1:2XfROY56AXnUqGsvl+6k29wrwsSbEh1lAouQB1vHpeE=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -1309,18 +1309,18 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/contrib/zpages v0.63.0 h1:TppOKuZGbqXMgsfjqq3i09N5Vbo1JLtLImUqiTPGnX4=
go.opentelemetry.io/contrib/zpages v0.63.0/go.mod h1:5F8uugz75ay/MMhRRhxAXY33FuaI8dl7jTxefrIy5qk=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE=
@@ -1333,8 +1333,8 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@@ -1482,8 +1482,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1744,10 +1744,10 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda h1:+2XxjfsAu6vqFxwGBRcHiMaDCuZiqXGDUDVWVtrFAnE=
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -1781,8 +1781,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI=
+65 -2
View File
@@ -17,13 +17,18 @@
// [test properties]: https://antithesis.com/docs/using_antithesis/properties/
// [workload]: https://antithesis.com/docs/getting_started/first_test/
// [antithesis-go-generator]: https://antithesis.com/docs/using_antithesis/sdk/go/instrumentor/
// [triage report]: https://antithesis.com/docs/reports/triage/
// [triage report]: https://antithesis.com/docs/reports/
// [here]: https://antithesis.com/docs/using_antithesis/sdk/fallback/
// [Sometimes assertions]: https://antithesis.com/docs/best_practices/sometimes_assertions/
//
// [details]: https://antithesis.com/docs/reports/triage/#details
// [details]: https://antithesis.com/docs/reports/
package assert
import (
"encoding/json"
"fmt"
)
type assertInfo struct {
Location *locationInfo `json:"location"`
Details map[string]any `json:"details"`
@@ -36,6 +41,64 @@ type assertInfo struct {
Condition bool `json:"condition"`
}
// Create a custom json marshaler for assertInfo so that we can force Errors to be marshaled with their error details.
// Without this, custom errors are marshaled as an empty object because the default json marshaler doesn't include the error
// (because it's a method - not an exported struct field).
func (f assertInfo) MarshalJSON() ([]byte, error) {
type alias assertInfo // prevent infinite recursion
a := alias(f)
if a.Details != nil {
a.Details = normalizeMap(a.Details)
}
return json.Marshal(a)
}
type jsonError struct {
innerError error
}
func (e jsonError) MarshalJSON() ([]byte, error) {
// Marshal this as the debug output string instead of e.Error(). These should be equivalent, but Sprintf correctly
// handles nil values for us (which otherwise are annoying to defend against due to this - https://go.dev/doc/faq#nil_error)
return json.Marshal(fmt.Sprintf("%+v", e.innerError))
}
// Recursively replace any `error` with jsonError while doing a deep copy.
// Most of the logic is in the normalize method below. This method exists to localize the type assertions
// and provide a function that takes in/out a map instead of any.
func normalizeMap(v map[string]any) map[string]any {
return normalize(v).(map[string]any)
}
func normalize(input any) any {
// This switch will miss some cases (pointers, structs, non-any types), but should catch a very large proportion of real error interfaces
// in real details objects. We can augment this if we find other cases common enough to support.
switch inputTyped := input.(type) {
case error:
// Check if the underlying error implements json.Marshaler, so that if the error
// already knows who to marshal itself, we don't override that.
if _, ok := inputTyped.(json.Marshaler); ok {
return inputTyped
} else {
return jsonError{inputTyped}
}
case map[string]any:
out := make(map[string]any, len(inputTyped))
for k, v := range inputTyped {
out[k] = normalize(v)
}
return out
case []any:
out := make([]any, len(inputTyped))
for i := range inputTyped {
out[i] = normalize(inputTyped[i])
}
return out
default:
return input
}
}
type wrappedAssertInfo struct {
A *assertInfo `json:"antithesis_assert"`
}
+4 -146
View File
@@ -4,67 +4,11 @@ package internal
import (
"encoding/json"
"fmt"
"log"
"math/rand"
"os"
"unsafe"
)
// --------------------------------------------------------------------------------
// To build and run an executable with this package
//
// CC=clang CGO_ENABLED=1 go run ./main.go
// --------------------------------------------------------------------------------
// \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
//
// The commented lines below, and the `import "C"` line which must directly follow
// the commented lines are used by CGO. They are load-bearing, and should not be
// changed without first understanding how CGO uses them.
//
// \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
// #cgo LDFLAGS: -ldl
//
// #include <dlfcn.h>
// #include <stdbool.h>
// #include <stdint.h>
// #include <stdlib.h>
//
// typedef void (*go_fuzz_json_data_fn)(const char *data, size_t size);
// void
// go_fuzz_json_data(void *f, const char *data, size_t size) {
// ((go_fuzz_json_data_fn)f)(data, size);
// }
//
// typedef void (*go_fuzz_flush_fn)(void);
// void
// go_fuzz_flush(void *f) {
// ((go_fuzz_flush_fn)f)();
// }
//
// typedef uint64_t (*go_fuzz_get_random_fn)(void);
// uint64_t
// go_fuzz_get_random(void *f) {
// return ((go_fuzz_get_random_fn)f)();
// }
//
// typedef bool (*go_notify_coverage_fn)(size_t);
// int
// go_notify_coverage(void *f, size_t edges) {
// bool b = ((go_notify_coverage_fn)f)(edges);
// return b ? 1 : 0;
// }
//
// typedef uint64_t (*go_init_coverage_fn)(size_t num_edges, const char *symbols);
// uint64_t
// go_init_coverage(void *f, size_t num_edges, const char *symbols) {
// return ((go_init_coverage_fn)f)(num_edges, symbols);
// }
//
import "C"
func Json_data(v any) error {
if data, err := json.Marshal(v); err != nil {
return err
@@ -95,45 +39,10 @@ type libHandler interface {
const (
errorLogLinePrefix = "[* antithesis-sdk-go *]"
defaultNativeLibraryPath = "/usr/lib/libvoidstar.so"
)
var handler libHandler
type voidstarHandler struct {
fuzzJsonData unsafe.Pointer
fuzzFlush unsafe.Pointer
fuzzGetRandom unsafe.Pointer
initCoverage unsafe.Pointer
notifyCoverage unsafe.Pointer
}
func (h *voidstarHandler) output(message string) {
msg_len := len(message)
if msg_len == 0 {
return
}
cstrMessage := C.CString(message)
defer C.free(unsafe.Pointer(cstrMessage))
C.go_fuzz_json_data(h.fuzzJsonData, cstrMessage, C.ulong(msg_len))
C.go_fuzz_flush(h.fuzzFlush)
}
func (h *voidstarHandler) random() uint64 {
return uint64(C.go_fuzz_get_random(h.fuzzGetRandom))
}
func (h *voidstarHandler) init_coverage(num_edge uint64, symbols string) uint64 {
cstrSymbols := C.CString(symbols)
defer C.free(unsafe.Pointer(cstrSymbols))
return uint64(C.go_init_coverage(h.initCoverage, C.ulong(num_edge), cstrSymbols))
}
func (h *voidstarHandler) notify(edge uint64) bool {
ival := int(C.go_notify_coverage(h.notifyCoverage, C.ulong(edge)))
return ival == 1
}
type localHandler struct {
outputFile *os.File // can be nil
}
@@ -160,63 +69,12 @@ func (h *localHandler) init_coverage(num_edges uint64, symbols string) uint64 {
return 0
}
// If we have a file at `defaultNativeLibraryPath`, we load the shared library
// (and panic on any error encountered during load).
// Otherwise fallback to the local handler.
func init() {
if _, err := os.Stat(defaultNativeLibraryPath); err == nil {
if handler, err = openSharedLib(defaultNativeLibraryPath); err != nil {
panic(err)
}
return
handler = init_in_antithesis()
if handler == nil {
// Otherwise fallback to the local handler.
handler = openLocalHandler()
}
handler = openLocalHandler()
}
// Attempt to load libvoidstar and some symbols from `path`
func openSharedLib(path string) (*voidstarHandler, error) {
cstrPath := C.CString(path)
defer C.free(unsafe.Pointer(cstrPath))
dlError := func(message string) error {
return fmt.Errorf("%s: (%s)", message, C.GoString(C.dlerror()))
}
sharedLib := C.dlopen(cstrPath, C.int(C.RTLD_NOW))
if sharedLib == nil {
return nil, dlError("Can not load the Antithesis native library")
}
loadFunc := func(name string) (symbol unsafe.Pointer, err error) {
cstrName := C.CString(name)
defer C.free(unsafe.Pointer(cstrName))
if symbol = C.dlsym(sharedLib, cstrName); symbol == nil {
err = dlError(fmt.Sprintf("Can not access symbol %s", name))
}
return
}
fuzzJsonData, err := loadFunc("fuzz_json_data")
if err != nil {
return nil, err
}
fuzzFlush, err := loadFunc("fuzz_flush")
if err != nil {
return nil, err
}
fuzzGetRandom, err := loadFunc("fuzz_get_random")
if err != nil {
return nil, err
}
notifyCoverage, err := loadFunc("notify_coverage")
if err != nil {
return nil, err
}
initCoverage, err := loadFunc("init_coverage_module")
if err != nil {
return nil, err
}
return &voidstarHandler{fuzzJsonData, fuzzFlush, fuzzGetRandom, initCoverage, notifyCoverage}, nil
}
// If `localOutputEnvVar` is set to a non-empty path, attempt to open that path and truncate the file
+1 -1
View File
@@ -3,7 +3,7 @@ package internal
// --------------------------------------------------------------------------------
// Versions
// --------------------------------------------------------------------------------
const SDK_Version = "0.4.3"
const SDK_Version = "0.5.0"
const Protocol_Version = "1.1.0"
// --------------------------------------------------------------------------------
@@ -0,0 +1,160 @@
//go:build enable_antithesis_sdk && linux && amd64 && cgo
package internal
import (
"fmt"
"unsafe"
"os"
)
// --------------------------------------------------------------------------------
// To build and run an executable with this package
//
// CC=clang CGO_ENABLED=1 go run ./main.go
// --------------------------------------------------------------------------------
// \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
//
// The commented lines below, and the `import "C"` line which must directly follow
// the commented lines are used by CGO. They are load-bearing, and should not be
// changed without first understanding how CGO uses them.
//
// \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
// #cgo LDFLAGS: -ldl
//
// #include <dlfcn.h>
// #include <stdbool.h>
// #include <stdint.h>
// #include <stdlib.h>
//
// typedef void (*go_fuzz_json_data_fn)(const char *data, size_t size);
// void
// go_fuzz_json_data(void *f, const char *data, size_t size) {
// ((go_fuzz_json_data_fn)f)(data, size);
// }
//
// typedef void (*go_fuzz_flush_fn)(void);
// void
// go_fuzz_flush(void *f) {
// ((go_fuzz_flush_fn)f)();
// }
//
// typedef uint64_t (*go_fuzz_get_random_fn)(void);
// uint64_t
// go_fuzz_get_random(void *f) {
// return ((go_fuzz_get_random_fn)f)();
// }
//
// typedef bool (*go_notify_coverage_fn)(size_t);
// int
// go_notify_coverage(void *f, size_t edges) {
// bool b = ((go_notify_coverage_fn)f)(edges);
// return b ? 1 : 0;
// }
//
// typedef uint64_t (*go_init_coverage_fn)(size_t num_edges, const char *symbols);
// uint64_t
// go_init_coverage(void *f, size_t num_edges, const char *symbols) {
// return ((go_init_coverage_fn)f)(num_edges, symbols);
// }
//
import "C"
const (
defaultNativeLibraryPath = "/usr/lib/libvoidstar.so"
)
type voidstarHandler struct {
fuzzJsonData unsafe.Pointer
fuzzFlush unsafe.Pointer
fuzzGetRandom unsafe.Pointer
initCoverage unsafe.Pointer
notifyCoverage unsafe.Pointer
}
func (h *voidstarHandler) output(message string) {
msg_len := len(message)
if msg_len == 0 {
return
}
cstrMessage := C.CString(message)
defer C.free(unsafe.Pointer(cstrMessage))
C.go_fuzz_json_data(h.fuzzJsonData, cstrMessage, C.ulong(msg_len))
C.go_fuzz_flush(h.fuzzFlush)
}
func (h *voidstarHandler) random() uint64 {
return uint64(C.go_fuzz_get_random(h.fuzzGetRandom))
}
func (h *voidstarHandler) init_coverage(num_edge uint64, symbols string) uint64 {
cstrSymbols := C.CString(symbols)
defer C.free(unsafe.Pointer(cstrSymbols))
return uint64(C.go_init_coverage(h.initCoverage, C.ulong(num_edge), cstrSymbols))
}
func (h *voidstarHandler) notify(edge uint64) bool {
ival := int(C.go_notify_coverage(h.notifyCoverage, C.ulong(edge)))
return ival == 1
}
// Attempt to load libvoidstar and some symbols from `path`
func openSharedLib(path string) (*voidstarHandler, error) {
cstrPath := C.CString(path)
defer C.free(unsafe.Pointer(cstrPath))
dlError := func(message string) error {
return fmt.Errorf("%s: (%s)", message, C.GoString(C.dlerror()))
}
sharedLib := C.dlopen(cstrPath, C.int(C.RTLD_NOW))
if sharedLib == nil {
return nil, dlError("Can not load the Antithesis native library")
}
loadFunc := func(name string) (symbol unsafe.Pointer, err error) {
cstrName := C.CString(name)
defer C.free(unsafe.Pointer(cstrName))
if symbol = C.dlsym(sharedLib, cstrName); symbol == nil {
err = dlError(fmt.Sprintf("Can not access symbol %s", name))
}
return
}
fuzzJsonData, err := loadFunc("fuzz_json_data")
if err != nil {
return nil, err
}
fuzzFlush, err := loadFunc("fuzz_flush")
if err != nil {
return nil, err
}
fuzzGetRandom, err := loadFunc("fuzz_get_random")
if err != nil {
return nil, err
}
notifyCoverage, err := loadFunc("notify_coverage")
if err != nil {
return nil, err
}
initCoverage, err := loadFunc("init_coverage_module")
if err != nil {
return nil, err
}
return &voidstarHandler{fuzzJsonData, fuzzFlush, fuzzGetRandom, initCoverage, notifyCoverage}, nil
}
// If we have a file at `defaultNativeLibraryPath`, we load the shared library
// (and panic on any error encountered during load).
func init_in_antithesis() libHandler {
if _, err := os.Stat(defaultNativeLibraryPath); err == nil {
handler, err := openSharedLib(defaultNativeLibraryPath)
if err != nil {
panic(err)
}
return handler
}
return nil
}
@@ -0,0 +1,7 @@
//go:build enable_antithesis_sdk && (!linux || !amd64 || !cgo)
package internal
func init_in_antithesis() libHandler {
return nil
}
+2
View File
@@ -40,6 +40,8 @@ var (
// ErrOpNotSupported is returned in general for operations that are not
// supported.
ErrOpNotSupported = getError(-C.EOPNOTSUPP)
// ErrNotImplemented indicates a function is not implemented in by libcephfs.
ErrNotImplemented = getError(-C.ENOSYS)
// Private errors:
-2
View File
@@ -1,5 +1,3 @@
//go:build ceph_preview
package cephfs
// Fd returns the integer open file descriptor in cephfs.
-2
View File
@@ -1,5 +1,3 @@
//go:build ceph_preview
package cephfs
// Futime changes file/directory last access and modification times.
+268
View File
@@ -0,0 +1,268 @@
//go:build ceph_preview
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#include <stdlib.h>
#include <dirent.h>
#include <cephfs/libcephfs.h>
// Types and constants are copied from libcephfs.h with added "_" as prefix. This
// prevents redefinition of the types on libcephfs versions that have them
// already.
typedef struct {
struct dirent dir_entry;
uint64_t snapid;
} _ceph_snapdiff_entry_t;
typedef struct {
struct ceph_mount_info* cmount;
struct ceph_dir_result* dir1;
struct ceph_dir_result* dir_aux;
} _ceph_snapdiff_info;
// open_snapdiff_fn matches the open_snapdiff function signature.
typedef int(*open_snapdiff_fn)(struct ceph_mount_info* cmount,
const char* root_path,
const char* rel_path,
const char* snap1,
const char* snap2,
_ceph_snapdiff_info* out);
// open_snapdiff_dlsym take *fn as open_snapdiff_fn and calls the dynamically loaded
// open_snapdiff function passed as 1st argument.
static inline int open_snapdiff_dlsym(void *fn,
struct ceph_mount_info* cmount,
const char* root_path,
const char* rel_path,
const char* snap1,
const char* snap2,
_ceph_snapdiff_info* out) {
// cast function pointer fn to open_snapdiff and call the function
return ((open_snapdiff_fn) fn)(cmount, root_path, rel_path, snap1, snap2, out);
}
// readdir_snapdiff_fn matches the readdir_snapdiff function signature.
typedef int(*readdir_snapdiff_fn)(_ceph_snapdiff_info* snapdiff,
_ceph_snapdiff_entry_t* out);
// readdir_snapdiff_dlsym take *fn as readdir_snapdiff_fn and calls the dynamically loaded
// readdir_snapdiff function passed as 1st argument.
static inline int readdir_snapdiff_dlsym(void *fn,
_ceph_snapdiff_info* snapdiff,
_ceph_snapdiff_entry_t* out) {
// cast function pointer fn to readdir_snapdiff and call the function
return ((readdir_snapdiff_fn) fn)(snapdiff, out);
}
// close_snapdiff_fn matches the close_snapdiff function signature.
typedef int(*close_snapdiff_fn)(_ceph_snapdiff_info* snapdiff);
// close_snapdiff_dlsym take *fn as close_snapdiff_fn and calls the dynamically loaded
// close_snapdiff function passed as 1st argument.
static inline int close_snapdiff_dlsym(void *fn,
_ceph_snapdiff_info* snapdiff) {
// cast function pointer fn to close_snapdiff and call the function
return ((close_snapdiff_fn) fn)(snapdiff);
}
*/
import "C"
import (
"fmt"
"sync"
"unsafe"
"github.com/ceph/go-ceph/internal/dlsym"
)
var (
cephOpenSnapDiffOnce sync.Once
cephReaddirSnapDiffOnce sync.Once
cephCloseSnapDiffOnce sync.Once
cephOpenSnapDiff unsafe.Pointer
cephReaddirSnapDiff unsafe.Pointer
cephCloseSnapDiff unsafe.Pointer
cephOpenSnapDiffErr error
cephReaddirSnapDiffErr error
cephCloseSnapDiffErr error
)
// SnapDiffInfo is a handle to a snapshot diff API.
type SnapDiffInfo struct {
cMount *MountInfo
dir1 *Directory
dirAux *Directory
}
// SnapDiffEntry is a single entry in the snapshot diff.
// It contains a DirEntry and the ID of the snapshot to
// which the directory belongs.
type SnapDiffEntry struct {
DirEntry *DirEntry
SnapID uint64
}
// SnapDiffConfig is used to define the parameters of a open_snapdiff call.
// Snapshot Delta is generated between the passed snapshots snap1 and snap2.
// All fields must be specified before passing to OpenSnapDiff().
type SnapDiffConfig struct {
// CMount is the ceph mount handle that will be used for snap.diff retrieval.
CMount *MountInfo
// RootPath represents the root path for snapshots-in-question.
RootPath string
// RelPath is the subpath under the root to build delta for.
RelPath string
// Snap1 is the first snapshot name.
Snap1 string
// Snap2 is the second snapshot name.
Snap2 string
}
// OpenSnapDiff opens a snapshot diff stream to get snapshots delta
// and returns a SnapDiffInfo struct containing the diff information.
//
// Implements:
//
// int ceph_open_snapdiff(struct ceph_mount_info* cmount,
// const char* root_path,
// const char* rel_path,
// const char* snap1,
// const char* snap2,
// struct ceph_snapdiff_info* out);
func OpenSnapDiff(config SnapDiffConfig) (*SnapDiffInfo, error) {
if config.CMount == nil || config.RootPath == "" || config.RelPath == "" ||
config.Snap1 == "" || config.Snap2 == "" {
return nil, errInvalid
}
cephOpenSnapDiffOnce.Do(func() {
cephOpenSnapDiff, cephOpenSnapDiffErr = dlsym.LookupSymbol("ceph_open_snapdiff")
})
if cephOpenSnapDiffErr != nil {
return nil, fmt.Errorf("%w: %w", ErrNotImplemented, cephOpenSnapDiffErr)
}
rawCephSnapDiffInfo := &C._ceph_snapdiff_info{}
ret := C.open_snapdiff_dlsym(
cephOpenSnapDiff,
config.CMount.mount,
C.CString(config.RootPath),
C.CString(config.RelPath),
C.CString(config.Snap1),
C.CString(config.Snap2),
rawCephSnapDiffInfo)
if ret != 0 {
return nil, getError(ret)
}
mountInfo := &MountInfo{
mount: rawCephSnapDiffInfo.cmount,
}
cephSnapDiffInfo := &SnapDiffInfo{
cMount: mountInfo,
dir1: &Directory{
mount: mountInfo,
dir: rawCephSnapDiffInfo.dir1,
},
dirAux: &Directory{
mount: mountInfo,
dir: rawCephSnapDiffInfo.dir_aux,
},
}
return cephSnapDiffInfo, nil
}
// validate checks that the SnapDiffInfo struct is valid.
func (info *SnapDiffInfo) validate() error {
if info.cMount == nil || info.dir1 == nil || info.dirAux == nil {
return errInvalid
}
return nil
}
// Readdir returns the next entry in the snapshot diff stream.
// If there are no more entries, it returns (nil, nil).
//
// Implements:
//
// int ceph_readdir_snapdiff(struct ceph_snapdiff_info* snapdiff,
// struct ceph_snapdiff_entry_t* out);
func (info *SnapDiffInfo) Readdir() (*SnapDiffEntry, error) {
if err := info.validate(); err != nil {
return nil, err
}
cephReaddirSnapDiffOnce.Do(func() {
cephReaddirSnapDiff, cephReaddirSnapDiffErr = dlsym.LookupSymbol("ceph_readdir_snapdiff")
})
if cephReaddirSnapDiffErr != nil {
return nil, fmt.Errorf("%w: %w", ErrNotImplemented, cephReaddirSnapDiffErr)
}
rawSnapDiffEntry := &C._ceph_snapdiff_entry_t{}
rawSnapDiffInfo := &C._ceph_snapdiff_info{
cmount: info.cMount.mount,
dir1: info.dir1.dir,
dir_aux: info.dirAux.dir,
}
ret := C.readdir_snapdiff_dlsym(
cephReaddirSnapDiff,
rawSnapDiffInfo,
rawSnapDiffEntry)
if ret < 0 {
return nil, getError(ret)
}
if ret == 0 {
// return 0 indicates there is not more entries to return.
return nil, nil
}
snapDiffEntry := &SnapDiffEntry{
DirEntry: toDirEntry(&rawSnapDiffEntry.dir_entry),
SnapID: uint64(rawSnapDiffEntry.snapid),
}
return snapDiffEntry, nil
}
// Close closes the snapshot diff handle.
//
// Implements:
//
// int ceph_close_snapdiff(struct ceph_snapdiff_info* snapdiff);
func (info *SnapDiffInfo) Close() error {
if err := info.validate(); err != nil {
return err
}
cephCloseSnapDiffOnce.Do(func() {
cephCloseSnapDiff, cephCloseSnapDiffErr = dlsym.LookupSymbol("ceph_close_snapdiff")
})
if cephCloseSnapDiffErr != nil {
return fmt.Errorf("%w: %w", ErrNotImplemented, cephCloseSnapDiffErr)
}
rawCephSnapDiffInfo := &C._ceph_snapdiff_info{
cmount: info.cMount.mount,
dir1: info.dir1.dir,
dir_aux: info.dirAux.dir,
}
ret := C.close_snapdiff_dlsym(
cephCloseSnapDiff,
rawCephSnapDiffInfo)
if ret != 0 {
return getError(ret)
}
return nil
}
+39
View File
@@ -0,0 +1,39 @@
package dlsym
// #cgo LDFLAGS: -ldl
//
// #define _GNU_SOURCE
//
// #include <stdlib.h>
// #include <dlfcn.h>
import "C"
import (
"errors"
"fmt"
"unsafe"
)
// ErrUndefinedSymbol is returned by LookupSymbol when the requested symbol
// could not be found.
var ErrUndefinedSymbol = errors.New("symbol not found")
// LookupSymbol resolves the named symbol from the already dynamically loaded
// libraries. If the symbol is found, a pointer to it is returned, in case of a
// failure, the message provided by dlerror() is included in the error message.
func LookupSymbol(symbol string) (unsafe.Pointer, error) {
cSymName := C.CString(symbol)
defer C.free(unsafe.Pointer(cSymName))
// clear dlerror before looking up the symbol
C.dlerror()
// resolve the address of the symbol
sym := C.dlsym(C.RTLD_DEFAULT, cSymName)
e := C.dlerror()
dlerr := C.GoString(e)
if dlerr != "" {
return nil, fmt.Errorf("%w: %s", ErrUndefinedSymbol, dlerr)
}
return sym, nil
}
+36
View File
@@ -1,5 +1,41 @@
version: "2"
run:
timeout: 5m
linters:
exclusions:
presets:
- std-error-handling
enable:
- gosec # Detects security problems.
# Keep all extras disabled for now to focus on the integer overflow problem.
# TODO: enable these and other good linters
- dogsled # Detects assignments with too many blank identifiers.
- errcheck
- errchkjson # Detects unsupported types passed to json encoding functions and reports if checks for the returned error can be omitted.
- exhaustive # Detects missing options in enum switch statements.
- gocyclo
- govet
- ineffassign
- makezero # Finds slice declarations with non-zero initial length.
- misspell # Detects commonly misspelled English words in comments.
- nakedret # Detects uses of naked returns.
- prealloc # Detects slice declarations that could potentially be pre-allocated.
- predeclared # Detects code that shadows one of Go's predeclared identifiers.
- reassign # Detects reassigning a top-level variable in another package.
- staticcheck
- thelper # Detects test helpers without t.Helper().
- tparallel # Detects inappropriate usage of t.Parallel().
- unconvert # Detects unnecessary type conversions.
- unused
- usestdlibvars # Detects the possibility to use variables/constants from the Go standard library.
- usetesting # Reports uses of functions with replacement inside the testing package.
settings:
govet:
disable:
- stdversion
gosec:
excludes:
- G404 # Weak random number generator used in tests.
- G304 # File inclusion
+3 -3
View File
@@ -70,13 +70,13 @@ If increasing the limit does not help, please
## Tests
In addition to unit tests,
[mimetype_tests](https://github.com/gabriel-vasile/mimetype_tests) compares the
library with the [Unix file utility](https://en.wikipedia.org/wiki/File_(command))
library with [libmagic](https://en.wikipedia.org/wiki/File_(command))
for around 50 000 sample files. Check the latest comparison results
[here](https://github.com/gabriel-vasile/mimetype_tests/actions).
## Benchmarks
Benchmarks for each file format are performed when a PR is open. The results can
be seen on the [workflows page](https://github.com/gabriel-vasile/mimetype/actions/workflows/benchmark.yml).
Benchmarks are performed when a PR is open. The results can be seen on the
[workflows page](https://github.com/gabriel-vasile/mimetype/actions/workflows/benchmark.yml).
Performance improvements are welcome but correctness is prioritized.
## Structure
-14
View File
@@ -94,20 +94,6 @@ func eq(path1, path2 [][]byte) bool {
return true
}
// LooksLikeObjectOrArray reports if first non white space character from raw
// is either { or [. Parsing raw as JSON is a heavy operation. When receiving some
// text input we can skip parsing if the input does not even look like JSON.
func LooksLikeObjectOrArray(raw []byte) bool {
for i := range raw {
if isSpace(raw[i]) {
continue
}
return raw[i] == '{' || raw[i] == '['
}
return false
}
// Parse will take out a parser from the pool depending on queryType and tries
// to parse raw bytes as JSON.
func Parse(queryType string, raw []byte) (parsed, inspected, firstToken int, querySatisfied bool) {
+13 -3
View File
@@ -106,10 +106,10 @@ func CRX(raw []byte, limit uint32) bool {
if len(raw) < minHeaderLen || !bytes.HasPrefix(raw, []byte("Cr24")) {
return false
}
pubkeyLen := binary.LittleEndian.Uint32(raw[8:12])
sigLen := binary.LittleEndian.Uint32(raw[12:16])
pubkeyLen := int64(binary.LittleEndian.Uint32(raw[8:12]))
sigLen := int64(binary.LittleEndian.Uint32(raw[12:16]))
zipOffset := minHeaderLen + pubkeyLen + sigLen
if uint32(len(raw)) < zipOffset {
if zipOffset < 0 || int64(len(raw)) < zipOffset {
return false
}
return Zip(raw[zipOffset:], limit)
@@ -209,3 +209,13 @@ func tarChksum(b []byte) (unsigned, signed int64) {
}
return unsigned, signed
}
// Zlib matches zlib compressed files.
func Zlib(raw []byte, _ uint32) bool {
// https://www.ietf.org/rfc/rfc6713.txt
// This check has one fault: ASCII code can satisfy it; for ex: []byte("x ")
zlib := len(raw) > 1 &&
raw[0] == 'x' && binary.BigEndian.Uint16(raw)%31 == 0
// Check that the file is not a regular text to avoid false positives.
return zlib && !Text(raw, 0)
}
+12
View File
@@ -0,0 +1,12 @@
package magic
import "bytes"
// GRIB matches a GRIdded Binary meteorological file.
// https://www.nco.ncep.noaa.gov/pmb/docs/on388/
// https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/
func GRIB(raw []byte, _ uint32) bool {
return len(raw) > 7 &&
bytes.HasPrefix(raw, []byte("GRIB")) &&
(raw[7] == 1 || raw[7] == 2)
}
+2 -1
View File
@@ -192,7 +192,8 @@ func matchOleClsid(in []byte, clsid []byte) bool {
// Expected offset of CLSID for root storage object.
clsidOffset := sectorLength*(1+firstSecID) + 80
if len(in) <= clsidOffset+16 {
// #731 offset is outside in or wrapped around due to integer overflow.
if len(in) <= clsidOffset+16 || clsidOffset < 0 {
return false
}
+67 -6
View File
@@ -283,7 +283,7 @@ func Shell(raw []byte, _ uint32) bool {
// Text matches a plain text file.
//
// TODO: This function does not parse BOM-less UTF16 and UTF32 files. Not really
// sure it should. Linux file utility also requires a BOM for UTF16 and UTF32.
// sure it should. libmagic also requires a BOM for UTF16 and UTF32.
func Text(raw []byte, _ uint32) bool {
// First look for BOM.
if cset := charset.FromBOM(raw); cset != "" {
@@ -352,13 +352,20 @@ func GLTF(raw []byte, limit uint32) bool {
return jsonHelper(raw, limit, json.QueryGLTF, json.TokObject)
}
func jsonHelper(raw []byte, limit uint32, q string, wantTok int) bool {
if !json.LooksLikeObjectOrArray(raw) {
func jsonHelper(raw scan.Bytes, limit uint32, q string, wantToks ...int) bool {
firstNonWS := raw.FirstNonWS()
hasTargetTok := false
for _, t := range wantToks {
hasTargetTok = hasTargetTok || (t&json.TokArray > 0 && firstNonWS == '[')
hasTargetTok = hasTargetTok || (t&json.TokObject > 0 && firstNonWS == '{')
}
if !hasTargetTok {
return false
}
lraw := len(raw)
parsed, inspected, firstToken, querySatisfied := json.Parse(q, raw)
if !querySatisfied || firstToken&wantTok == 0 {
parsed, inspected, _, querySatisfied := json.Parse(q, raw)
if !querySatisfied {
return false
}
@@ -369,7 +376,7 @@ func jsonHelper(raw []byte, limit uint32, q string, wantTok int) bool {
// If a section of the file was provided, check if all of it was inspected.
// In other words, check that if there was a problem parsing, that problem
// occured at the last byte in the input.
// occurred at the last byte in the input.
return inspected == lraw && lraw > 0
}
@@ -536,3 +543,57 @@ func Vtt(raw []byte, limit uint32) bool {
return bytes.Equal(raw, []byte{0xEF, 0xBB, 0xBF, 0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) || // UTF-8 BOM and "WEBVTT"
bytes.Equal(raw, []byte{0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) // "WEBVTT"
}
type rfc822Hint struct {
h []byte
matchFlags scan.Flags
}
// The hints come from libmagic, but the implementation is bit different. libmagic
// only checks if the file starts with the hint, while we additionally look for
// a secondary hint in the first few lines of input.
func RFC822(raw []byte, limit uint32) bool {
b := scan.Bytes(raw)
// Keep hints here to avoid instantiating them several times in lineHasRFC822Hint.
// The alternative is to make them a package level var, but then they'd go
// on the heap.
// Some of the hints are IgnoreCase, some not. I selected based on what libmagic
// does and based on personal observations from sample files.
hints := []rfc822Hint{
{[]byte("From: "), 0},
{[]byte("To: "), 0},
{[]byte("CC: "), scan.IgnoreCase},
{[]byte("Date: "), 0},
{[]byte("Subject: "), 0},
{[]byte("Received: "), 0},
{[]byte("Relay-Version: "), 0},
{[]byte("#! rnews"), 0},
{[]byte("N#! rnews"), 0},
{[]byte("Forward to"), 0},
{[]byte("Pipe to"), 0},
{[]byte("DELIVERED-TO: "), scan.IgnoreCase},
{[]byte("RETURN-PATH: "), scan.IgnoreCase},
{[]byte("Content-Type: "), 0},
{[]byte("Content-Transfer-Encoding: "), 0},
}
if !lineHasRFC822Hint(b.Line(), hints) {
return false
}
for i := 0; i < 20; i++ {
if lineHasRFC822Hint(b.Line(), hints) {
return true
}
}
return false
}
func lineHasRFC822Hint(b scan.Bytes, hints []rfc822Hint) bool {
for _, h := range hints {
if b.Match(h.h, h.matchFlags) > -1 {
return true
}
}
return false
}
+3 -3
View File
@@ -69,9 +69,9 @@ func isFileTypeNamePresent(in []byte, flType string) bool {
// vintWidth parses the variable-integer width in matroska containers
func vintWidth(v int) int {
mask, max, num := 128, 8, 1
for num < max && v&mask == 0 {
mask = mask >> 1
mask, nTimes, num := 128, 8, 1
for num < nTimes && v&mask == 0 {
mask >>= 1
num++
}
return num
+14 -7
View File
@@ -85,10 +85,14 @@ func Zip(raw []byte, limit uint32) bool {
// (instead of relying on offsets told by the file.)
func Jar(raw []byte, limit uint32) bool {
return executableJar(raw) ||
// First entry must be an empty META-INF directory or the manifest.
// There is no specification saying that, but the jar reader and writer
// implementations from Java do it that way.
// https://github.com/openjdk/jdk/blob/88c4678eed818cbe9380f35352e90883fed27d33/src/java.base/share/classes/java/util/jar/JarInputStream.java#L170-L173
zipHas(raw, zipEntries{{
name: []byte("META-INF/MANIFEST.MF"),
}, {
name: []byte("META-INF/"),
}, {
name: []byte("META-INF/MANIFEST.MF"),
}}, 1)
}
@@ -127,11 +131,14 @@ type zipEntries []struct {
func (z zipEntries) match(file []byte) bool {
for i := range z {
if z[i].dir && bytes.HasPrefix(file, z[i].name) {
return true
}
if bytes.Equal(file, z[i].name) {
return true
if z[i].dir {
if bytes.HasPrefix(file, z[i].name) {
return true
}
} else {
if bytes.Equal(file, z[i].name) {
return true
}
}
}
return false
+19 -6
View File
@@ -35,6 +35,19 @@ func (b *Bytes) TrimRWS() {
}
}
// FirstNonWS returns the first non-whitespace character from b,
// or 0x00 if no such character is found.
func (b Bytes) FirstNonWS() byte {
for i := range b {
if ByteIsWS(b[i]) {
continue
}
return b[i]
}
return 0x00
}
// Peek one byte from b or 0x00 if b is empty.
func (b *Bytes) Peek() byte {
if len(*b) > 0 {
@@ -63,8 +76,8 @@ func (b *Bytes) PopN(n int) []byte {
return nil
}
// PopUntil will advance b until, but not including, the first occurence of stopAt
// character. If no occurence is found, then it will advance until the end of b.
// PopUntil will advance b until, but not including, the first occurrence of stopAt
// character. If no occurrence is found, then it will advance until the end of b.
// The returned Bytes is a slice of all the bytes that we're advanced over.
func (b *Bytes) PopUntil(stopAt ...byte) Bytes {
if len(*b) == 0 {
@@ -77,7 +90,7 @@ func (b *Bytes) PopUntil(stopAt ...byte) Bytes {
prefix := (*b)[:i]
*b = (*b)[i:]
return Bytes(prefix)
return prefix
}
// ReadSlice is the same as PopUntil, but the returned value includes stopAt as well.
@@ -94,7 +107,7 @@ func (b *Bytes) ReadSlice(stopAt byte) Bytes {
prefix := (*b)[:i]
*b = (*b)[i:]
return Bytes(prefix)
return prefix
}
// Line returns the first line from b and advances b with the length of the
@@ -117,7 +130,7 @@ func (b *Bytes) Line() Bytes {
// If b length is less than readLimit, it means we received an incomplete file
// and proceed with dropping the last line.
func (b *Bytes) DropLastLine(readLimit uint32) {
if readLimit == 0 || uint32(len(*b)) < readLimit {
if readLimit == 0 || uint64(len(*b)) < uint64(readLimit) {
return
}
@@ -151,7 +164,7 @@ const (
FullWord
)
// Search for occurences of pattern p inside b at any index.
// Search for occurrences of pattern p inside b at any index.
// It returns the index where p was found in b and how many bytes were needed
// for matching the pattern.
func (b Bytes) Search(p []byte, flags Flags) (i int, l int) {
+5 -8
View File
@@ -2,6 +2,7 @@ package mimetype
import (
"mime"
"slices"
"strings"
"github.com/gabriel-vasile/mimetype/internal/charset"
@@ -58,10 +59,8 @@ func (m *MIME) Is(expectedMIME string) bool {
return true
}
for _, alias := range m.aliases {
if alias == expectedMIME {
return true
}
if slices.Contains(m.aliases, expectedMIME) {
return true
}
return false
@@ -180,10 +179,8 @@ func (m *MIME) lookup(mime string) *MIME {
if mime == m.mime {
return m
}
for _, n := range m.aliases {
if n == mime {
return m
}
if slices.Contains(m.aliases, mime) {
return m
}
for _, c := range m.children {
+4 -1
View File
@@ -1,4 +1,4 @@
## 192 Supported MIME types
## 195 Supported MIME types
This file is automatically generated when running tests. Do not edit manually.
Extension | MIME type <br> Aliases | Hierarchy
@@ -152,6 +152,8 @@ Extension | MIME type <br> Aliases | Hierarchy
**.chm** | **application/vnd.ms-htmlhelp** | chm>root
**.wpd** | **application/vnd.wordperfect** | wpd>root
**.dxf** | **image/vnd.dxf** | dxf>root
**.grb** | **application/grib** | grb>root
**n/a** | **application/zlib** | zlib>root
**.txt** | **text/plain** | txt>root
**.svg** | **image/svg+xml** | svg>txt>root
**.html** | **text/html** | html>txt>root
@@ -195,3 +197,4 @@ Extension | MIME type <br> Aliases | Hierarchy
**.pgm** | **image/x-portable-graymap** | pgm>txt>root
**.ppm** | **image/x-portable-pixmap** | ppm>txt>root
**.pam** | **image/x-portable-arbitrarymap** | pam>txt>root
**.eml** | **message/rfc822** | eml>txt>root
+5 -2
View File
@@ -24,7 +24,7 @@ var root = newMIME("application/octet-stream", "",
woff2, otf, ttc, eot, wasm, shx, dbf, dcm, rar, djvu, mobi, lit, bpg, cbor,
sqlite3, dwg, nes, lnk, macho, qcp, icns, hdr, mrc, mdb, accdb, zstd, cab,
rpm, xz, lzip, torrent, cpio, tzif, xcf, pat, gbr, glb, cabIS, jxr, parquet,
oneNote, chm, wpd, dxf,
oneNote, chm, wpd, dxf, grib, zlib,
// Keep text last because it is the slowest check.
text,
)
@@ -82,7 +82,7 @@ var (
alias("application/x-ogg")
oggAudio = newMIME("audio/ogg", ".oga", magic.OggAudio)
oggVideo = newMIME("video/ogg", ".ogv", magic.OggVideo)
text = newMIME("text/plain", ".txt", magic.Text, svg, html, xml, php, js, lua, perl, python, ruby, json, ndJSON, rtf, srt, tcl, csv, tsv, vCard, iCalendar, warc, vtt, shell, netpbm, netpgm, netppm, netpam)
text = newMIME("text/plain", ".txt", magic.Text, svg, html, xml, php, js, lua, perl, python, ruby, json, ndJSON, rtf, srt, tcl, csv, tsv, vCard, iCalendar, warc, vtt, shell, netpbm, netpgm, netppm, netpam, rfc822)
xml = newMIME("text/xml", ".xml", magic.XML, rss, atom, x3d, kml, xliff, collada, gml, gpx, tcx, amf, threemf, xfdf, owl2, xhtml).
alias("application/xml")
xhtml = newMIME("application/xhtml+xml", ".html", magic.XHTML)
@@ -287,4 +287,7 @@ var (
chm = newMIME("application/vnd.ms-htmlhelp", ".chm", magic.CHM)
wpd = newMIME("application/vnd.wordperfect", ".wpd", magic.WPD)
dxf = newMIME("image/vnd.dxf", ".dxf", magic.DXF)
rfc822 = newMIME("message/rfc822", ".eml", magic.RFC822)
grib = newMIME("application/grib", ".grb", magic.GRIB)
zlib = newMIME("application/zlib", "", magic.Zlib)
)
+1
View File
@@ -32,6 +32,7 @@ linters:
- maintidx
- misspell
- mnd
- modernize
- nakedret
- nestif
- nilnil
+4 -1
View File
@@ -123,6 +123,7 @@ validate := validator.New(validator.WithRequiredStructEnabled())
| udp6_addr | User Datagram Protocol Address UDPv6 |
| udp_addr | User Datagram Protocol Address UDP |
| unix_addr | Unix domain socket end point Address |
| uds_exists | Unix domain socket exists (checks filesystem sockets and Linux abstract sockets) |
| uri | URI String |
| url | URL String |
| http_url | HTTP(s) URL String |
@@ -137,6 +138,7 @@ validate := validator.New(validator.WithRequiredStructEnabled())
| alpha | Alpha Only |
| alphaspace | Alpha Space |
| alphanum | Alphanumeric |
| alphanumspace | Alphanumeric Space |
| alphanumunicode | Alphanumeric Unicode |
| alphaunicode | Alpha Unicode |
| ascii | ASCII |
@@ -164,7 +166,8 @@ validate := validator.New(validator.WithRequiredStructEnabled())
| base64 | Base64 String |
| base64url | Base64URL String |
| base64rawurl | Base64RawURL String |
| bic | Business Identifier Code (ISO 9362) |
| bic_iso_9362_2014 | Business Identifier Code (ISO 9362:2014) |
| bic | Business Identifier Code (ISO 9362:2022) |
| bcp47_language_tag | Language tag (BCP 47) |
| btc_addr | Bitcoin Address |
| btc_addr_bech32 | Bitcoin Bech32 Address (segwit) |
+99 -9
View File
@@ -1,6 +1,7 @@
package validator
import (
"bufio"
"bytes"
"cmp"
"context"
@@ -15,6 +16,7 @@ import (
"net/url"
"os"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
@@ -120,6 +122,7 @@ var (
"alpha": isAlpha,
"alphaspace": isAlphaSpace,
"alphanum": isAlphanum,
"alphanumspace": isAlphaNumericSpace,
"alphaunicode": isAlphaUnicode,
"alphanumunicode": isAlphanumUnicode,
"boolean": isBoolean,
@@ -205,6 +208,7 @@ var (
"ip6_addr": isIP6AddrResolvable,
"ip_addr": isIPAddrResolvable,
"unix_addr": isUnixAddrResolvable,
"uds_exists": isUnixDomainSocketExists,
"mac": isMAC,
"hostname": isHostnameRFC952, // RFC 952
"hostname_rfc1123": isHostnameRFC1123, // RFC 1123
@@ -237,7 +241,8 @@ var (
"bcp47_language_tag": isBCP47LanguageTag,
"postcode_iso3166_alpha2": isPostcodeByIso3166Alpha2,
"postcode_iso3166_alpha2_field": isPostcodeByIso3166Alpha2Field,
"bic": isIsoBicFormat,
"bic_iso_9362_2014": isIsoBic2014Format,
"bic": isIsoBic2022Format,
"semver": isSemverFormat,
"dns_rfc1035_label": isDnsRFC1035LabelFormat,
"credit_card": isCreditCard,
@@ -533,12 +538,20 @@ func hasMultiByteCharacter(fl FieldLevel) bool {
// isPrintableASCII is the validation function for validating if the field's value is a valid printable ASCII character.
func isPrintableASCII(fl FieldLevel) bool {
return printableASCIIRegex().MatchString(fl.Field().String())
field := fl.Field()
if field.Kind() == reflect.String {
return printableASCIIRegex().MatchString(field.String())
}
return false
}
// isASCII is the validation function for validating if the field's value is a valid ASCII character.
func isASCII(fl FieldLevel) bool {
return aSCIIRegex().MatchString(fl.Field().String())
field := fl.Field()
if field.Kind() == reflect.String {
return aSCIIRegex().MatchString(field.String())
}
return false
}
// isUUID5 is the validation function for validating if the field's value is a valid v5 UUID.
@@ -1773,6 +1786,11 @@ func isAlphaSpace(fl FieldLevel) bool {
return alphaSpaceRegex().MatchString(fl.Field().String())
}
// isAlphaNumericSpace is the validation function for validating if the current field's value is a valid alphanumeric value with spaces.
func isAlphaNumericSpace(fl FieldLevel) bool {
return alphanNumericSpaceRegex().MatchString(fl.Field().String())
}
// isAlphaUnicode is the validation function for validating if the current field's value is a valid alpha unicode value.
func isAlphaUnicode(fl FieldLevel) bool {
return alphaUnicodeRegex().MatchString(fl.Field().String())
@@ -1974,11 +1992,12 @@ func excludedUnless(fl FieldLevel) bool {
panic(fmt.Sprintf("Bad param number for excluded_unless %s", fl.FieldName()))
}
for i := 0; i < len(params); i += 2 {
if !requireCheckFieldValue(fl, params[i], params[i+1], false) {
return !hasValue(fl)
if requireCheckFieldValue(fl, params[i], params[i+1], false) {
return true
}
}
return true
return !hasValue(fl)
}
// excludedWith is the validation function
@@ -2595,6 +2614,70 @@ func isUnixAddrResolvable(fl FieldLevel) bool {
return err == nil
}
// isUnixDomainSocketExists is the validation function for validating if the field's value is an existing Unix domain socket.
// It handles both filesystem-based sockets and Linux abstract sockets.
// It always returns false for Windows.
func isUnixDomainSocketExists(fl FieldLevel) bool {
if runtime.GOOS == "windows" {
return false
}
sockpath := fl.Field().String()
if sockpath == "" {
return false
}
// On Linux, check for abstract sockets (prefixed with @)
if runtime.GOOS == "linux" && strings.HasPrefix(sockpath, "@") {
return isAbstractSocketExists(sockpath)
}
// For filesystem-based sockets, check if the path exists and is a socket
stats, err := os.Stat(sockpath)
if err != nil {
return false
}
return stats.Mode().Type() == fs.ModeSocket
}
// isAbstractSocketExists checks if a Linux abstract socket exists by reading /proc/net/unix.
// Abstract sockets are identified by an @ prefix in human-readable form.
func isAbstractSocketExists(sockpath string) bool {
file, err := os.Open("/proc/net/unix")
if err != nil {
return false
}
defer func() {
_ = file.Close()
}()
scanner := bufio.NewScanner(file)
// Skip the header line
if !scanner.Scan() {
return false
}
// Abstract sockets in /proc/net/unix are represented with @ prefix
// The socket path is the last field in each line
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
// The path is the last field (8th field typically)
if len(fields) >= 8 {
path := fields[len(fields)-1]
if path == sockpath {
return true
}
}
}
return false
}
func isIP4Addr(fl FieldLevel) bool {
val := fl.Field().String()
@@ -2943,11 +3026,18 @@ func isBCP47LanguageTag(fl FieldLevel) bool {
panic(fmt.Sprintf("Bad field type %s", field.Type()))
}
// isIsoBicFormat is the validation function for validating if the current field's value is a valid Business Identifier Code (SWIFT code), defined in ISO 9362
func isIsoBicFormat(fl FieldLevel) bool {
// isIsoBic2014Format is the validation function for validating if the current field's value is a valid Business Identifier Code (SWIFT code), defined in ISO 9362 2014
func isIsoBic2014Format(fl FieldLevel) bool {
bicString := fl.Field().String()
return bicRegex().MatchString(bicString)
return bic2014Regex().MatchString(bicString)
}
// isIsoBic2022Format is the validation function for validating if the current field's value is a valid Business Identifier Code (SWIFT code), defined in ISO 9362 2022
func isIsoBic2022Format(fl FieldLevel) bool {
bicString := fl.Field().String()
return bic2022Regex().MatchString(bicString)
}
// isSemverFormat is the validation function for validating if the current field's value is a valid semver version, defined in Semantic Versioning 2.0.0
+18
View File
@@ -289,6 +289,24 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
if wrapper, ok := v.validations[current.tag]; ok {
current.fn = wrapper.fn
current.runValidationWhenNil = wrapper.runValidationOnNil
} else if aliasTag, isAlias := v.aliases[current.tag]; isAlias {
aliasFirst, aliasLast := v.parseFieldTagsRecursive(aliasTag, fieldName, current.tag, true)
current.tag = aliasFirst.tag
current.fn = aliasFirst.fn
current.runValidationWhenNil = aliasFirst.runValidationWhenNil
current.hasParam = aliasFirst.hasParam
current.param = aliasFirst.param
current.typeof = aliasFirst.typeof
current.hasAlias = true
if aliasFirst.next != nil {
nextInChain := current.next
current.next = aliasFirst.next
aliasLast.next = nextInChain
aliasLast.isBlockEnd = false
current = aliasLast
}
} else {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, current.tag, fieldName)))
}
+6 -6
View File
@@ -258,7 +258,7 @@ var iso3166_2 = map[string]struct{}{
"BD-56": {}, "BD-57": {}, "BD-58": {}, "BD-59": {}, "BD-60": {},
"BD-61": {}, "BD-62": {}, "BD-63": {}, "BD-64": {}, "BD-A": {},
"BD-B": {}, "BD-C": {}, "BD-D": {}, "BD-E": {}, "BD-F": {},
"BD-G": {}, "BE-BRU": {}, "BE-VAN": {}, "BE-VBR": {}, "BE-VLG": {},
"BD-G": {}, "BD-H": {}, "BE-BRU": {}, "BE-VAN": {}, "BE-VBR": {}, "BE-VLG": {},
"BE-VLI": {}, "BE-VOV": {}, "BE-VWV": {}, "BE-WAL": {}, "BE-WBR": {},
"BE-WHT": {}, "BE-WLG": {}, "BE-WLX": {}, "BE-WNA": {}, "BF-01": {},
"BF-02": {}, "BF-03": {}, "BF-04": {}, "BF-05": {}, "BF-06": {},
@@ -573,7 +573,7 @@ var iso3166_2 = map[string]struct{}{
"ID-KB": {}, "ID-KI": {}, "ID-KU": {}, "ID-KR": {}, "ID-KS": {},
"ID-KT": {}, "ID-LA": {}, "ID-MA": {}, "ID-ML": {}, "ID-MU": {},
"ID-NB": {}, "ID-NT": {}, "ID-NU": {}, "ID-PA": {}, "ID-PB": {},
"ID-PE": {}, "ID-PP": {}, "ID-PS": {}, "ID-PT": {}, "ID-RI": {},
"ID-PD": {}, "ID-PE": {}, "ID-PP": {}, "ID-PS": {}, "ID-PT": {}, "ID-RI": {},
"ID-SA": {}, "ID-SB": {}, "ID-SG": {}, "ID-SL": {}, "ID-SM": {},
"ID-SN": {}, "ID-SR": {}, "ID-SS": {}, "ID-ST": {}, "ID-SU": {},
"ID-YO": {}, "IE-C": {}, "IE-CE": {}, "IE-CN": {}, "IE-CO": {},
@@ -587,7 +587,7 @@ var iso3166_2 = map[string]struct{}{
"IN-AS": {}, "IN-BR": {}, "IN-CH": {}, "IN-CT": {}, "IN-DH": {},
"IN-DL": {}, "IN-DN": {}, "IN-GA": {}, "IN-GJ": {}, "IN-HP": {},
"IN-HR": {}, "IN-JH": {}, "IN-JK": {}, "IN-KA": {}, "IN-KL": {},
"IN-LD": {}, "IN-MH": {}, "IN-ML": {}, "IN-MN": {}, "IN-MP": {},
"IN-LA": {}, "IN-LD": {}, "IN-MH": {}, "IN-ML": {}, "IN-MN": {}, "IN-MP": {},
"IN-MZ": {}, "IN-NL": {}, "IN-TG": {}, "IN-OR": {}, "IN-PB": {}, "IN-PY": {},
"IN-RJ": {}, "IN-SK": {}, "IN-TN": {}, "IN-TR": {}, "IN-UP": {},
"IN-UT": {}, "IN-WB": {}, "IQ-AN": {}, "IQ-AR": {}, "IQ-BA": {},
@@ -667,7 +667,7 @@ var iso3166_2 = map[string]struct{}{
"KP-08": {}, "KP-09": {}, "KP-10": {}, "KP-13": {}, "KR-11": {},
"KR-26": {}, "KR-27": {}, "KR-28": {}, "KR-29": {}, "KR-30": {},
"KR-31": {}, "KR-41": {}, "KR-42": {}, "KR-43": {}, "KR-44": {},
"KR-45": {}, "KR-46": {}, "KR-47": {}, "KR-48": {}, "KR-49": {},
"KR-45": {}, "KR-46": {}, "KR-47": {}, "KR-48": {}, "KR-49": {}, "KR-50": {},
"KW-AH": {}, "KW-FA": {}, "KW-HA": {}, "KW-JA": {}, "KW-KU": {},
"KW-MU": {}, "KZ-10": {}, "KZ-75": {}, "KZ-19": {}, "KZ-11": {},
"KZ-15": {}, "KZ-71": {}, "KZ-23": {}, "KZ-27": {}, "KZ-47": {},
@@ -758,7 +758,7 @@ var iso3166_2 = map[string]struct{}{
"ME-02": {}, "ME-03": {}, "ME-04": {}, "ME-05": {}, "ME-06": {},
"ME-07": {}, "ME-08": {}, "ME-09": {}, "ME-10": {}, "ME-11": {},
"ME-12": {}, "ME-13": {}, "ME-14": {}, "ME-15": {}, "ME-16": {},
"ME-17": {}, "ME-18": {}, "ME-19": {}, "ME-20": {}, "ME-21": {}, "ME-24": {},
"ME-17": {}, "ME-18": {}, "ME-19": {}, "ME-20": {}, "ME-21": {}, "ME-22": {}, "ME-23": {}, "ME-24": {}, "ME-25": {},
"MG-A": {}, "MG-D": {}, "MG-F": {}, "MG-M": {}, "MG-T": {},
"MG-U": {}, "MH-ALK": {}, "MH-ALL": {}, "MH-ARN": {}, "MH-AUR": {},
"MH-EBO": {}, "MH-ENI": {}, "MH-JAB": {}, "MH-JAL": {}, "MH-KIL": {},
@@ -856,7 +856,7 @@ var iso3166_2 = map[string]struct{}{
"NO-22": {}, "NP-1": {}, "NP-2": {}, "NP-3": {}, "NP-4": {},
"NP-5": {}, "NP-BA": {}, "NP-BH": {}, "NP-DH": {}, "NP-GA": {},
"NP-JA": {}, "NP-KA": {}, "NP-KO": {}, "NP-LU": {}, "NP-MA": {},
"NP-ME": {}, "NP-NA": {}, "NP-RA": {}, "NP-SA": {}, "NP-SE": {},
"NP-ME": {}, "NP-NA": {}, "NP-P1": {}, "NP-P2": {}, "NP-P3": {}, "NP-P4": {}, "NP-P5": {}, "NP-P6": {}, "NP-P7": {}, "NP-RA": {}, "NP-SA": {}, "NP-SE": {},
"NR-01": {}, "NR-02": {}, "NR-03": {}, "NR-04": {}, "NR-05": {},
"NR-06": {}, "NR-07": {}, "NR-08": {}, "NR-09": {}, "NR-10": {},
"NR-11": {}, "NR-12": {}, "NR-13": {}, "NR-14": {}, "NZ-AUK": {},
+58 -58
View File
@@ -10,33 +10,33 @@ var iso4217 = map[string]struct{}{
"BIF": {}, "CVE": {}, "KHR": {}, "XAF": {}, "CAD": {},
"KYD": {}, "CLP": {}, "CLF": {}, "CNY": {}, "COP": {},
"COU": {}, "KMF": {}, "CDF": {}, "NZD": {}, "CRC": {},
"HRK": {}, "CUP": {}, "CUC": {}, "ANG": {}, "CZK": {},
"DKK": {}, "DJF": {}, "DOP": {}, "EGP": {}, "SVC": {},
"ERN": {}, "SZL": {}, "ETB": {}, "FKP": {}, "FJD": {},
"XPF": {}, "GMD": {}, "GEL": {}, "GHS": {}, "GIP": {},
"GTQ": {}, "GBP": {}, "GNF": {}, "GYD": {}, "HTG": {},
"HNL": {}, "HKD": {}, "HUF": {}, "ISK": {}, "IDR": {},
"XDR": {}, "IRR": {}, "IQD": {}, "ILS": {}, "JMD": {},
"JPY": {}, "JOD": {}, "KZT": {}, "KES": {}, "KPW": {},
"KRW": {}, "KWD": {}, "KGS": {}, "LAK": {}, "LBP": {},
"LSL": {}, "ZAR": {}, "LRD": {}, "LYD": {}, "CHF": {},
"MOP": {}, "MKD": {}, "MGA": {}, "MWK": {}, "MYR": {},
"MVR": {}, "MRU": {}, "MUR": {}, "XUA": {}, "MXN": {},
"MXV": {}, "MDL": {}, "MNT": {}, "MAD": {}, "MZN": {},
"MMK": {}, "NAD": {}, "NPR": {}, "NIO": {}, "NGN": {},
"OMR": {}, "PKR": {}, "PAB": {}, "PGK": {}, "PYG": {},
"PEN": {}, "PHP": {}, "PLN": {}, "QAR": {}, "RON": {},
"RUB": {}, "RWF": {}, "SHP": {}, "WST": {}, "STN": {},
"SAR": {}, "RSD": {}, "SCR": {}, "SLL": {}, "SGD": {},
"XSU": {}, "SBD": {}, "SOS": {}, "SSP": {}, "LKR": {},
"SDG": {}, "SRD": {}, "SEK": {}, "CHE": {}, "CHW": {},
"SYP": {}, "TWD": {}, "TJS": {}, "TZS": {}, "THB": {},
"TOP": {}, "TTD": {}, "TND": {}, "TRY": {}, "TMT": {},
"UGX": {}, "UAH": {}, "AED": {}, "USN": {}, "UYU": {},
"UYI": {}, "UYW": {}, "UZS": {}, "VUV": {}, "VES": {},
"VND": {}, "YER": {}, "ZMW": {}, "ZWL": {}, "XBA": {},
"XBB": {}, "XBC": {}, "XBD": {}, "XTS": {}, "XXX": {},
"XAU": {}, "XPD": {}, "XPT": {}, "XAG": {},
"CUP": {}, "CZK": {}, "DKK": {}, "DJF": {}, "DOP": {},
"EGP": {}, "SVC": {}, "ERN": {}, "SZL": {}, "ETB": {},
"FKP": {}, "FJD": {}, "XPF": {}, "GMD": {}, "GEL": {},
"GHS": {}, "GIP": {}, "GTQ": {}, "GBP": {}, "GNF": {},
"GYD": {}, "HTG": {}, "HNL": {}, "HKD": {}, "HUF": {},
"ISK": {}, "IDR": {}, "XDR": {}, "IRR": {}, "IQD": {},
"ILS": {}, "JMD": {}, "JPY": {}, "JOD": {}, "KZT": {},
"KES": {}, "KPW": {}, "KRW": {}, "KWD": {}, "KGS": {},
"LAK": {}, "LBP": {}, "LSL": {}, "ZAR": {}, "LRD": {},
"LYD": {}, "CHF": {}, "MOP": {}, "MKD": {}, "MGA": {},
"MWK": {}, "MYR": {}, "MVR": {}, "MRU": {}, "MUR": {},
"XUA": {}, "MXN": {}, "MXV": {}, "MDL": {}, "MNT": {},
"MAD": {}, "MZN": {}, "MMK": {}, "NAD": {}, "NPR": {},
"NIO": {}, "NGN": {}, "OMR": {}, "PKR": {}, "PAB": {},
"PGK": {}, "PYG": {}, "PEN": {}, "PHP": {}, "PLN": {},
"QAR": {}, "RON": {}, "RUB": {}, "RWF": {}, "SHP": {},
"WST": {}, "STN": {}, "SAR": {}, "RSD": {}, "SCR": {},
"SLE": {}, "SGD": {}, "XSU": {}, "SBD": {}, "SOS": {},
"SSP": {}, "LKR": {}, "SDG": {}, "SRD": {}, "SEK": {},
"CHE": {}, "CHW": {}, "SYP": {}, "TWD": {}, "TJS": {},
"TZS": {}, "THB": {}, "TOP": {}, "TTD": {}, "TND": {},
"TRY": {}, "TMT": {}, "UGX": {}, "UAH": {}, "AED": {},
"USN": {}, "UYU": {}, "UYI": {}, "UYW": {}, "UZS": {},
"VUV": {}, "VES": {}, "VED": {}, "VND": {}, "YER": {},
"ZMW": {}, "ZWG": {}, "XBA": {}, "XBB": {}, "XBC": {},
"XBD": {}, "XCG": {}, "XTS": {}, "XXX": {}, "XAU": {},
"XPD": {}, "XPT": {}, "XAG": {},
}
var iso4217_numeric = map[int]struct{}{
@@ -45,35 +45,35 @@ var iso4217_numeric = map[int]struct{}{
64: {}, 68: {}, 72: {}, 84: {}, 90: {},
96: {}, 104: {}, 108: {}, 116: {}, 124: {},
132: {}, 136: {}, 144: {}, 152: {}, 156: {},
170: {}, 174: {}, 188: {}, 191: {}, 192: {},
203: {}, 208: {}, 214: {}, 222: {}, 230: {},
232: {}, 238: {}, 242: {}, 262: {}, 270: {},
292: {}, 320: {}, 324: {}, 328: {}, 332: {},
340: {}, 344: {}, 348: {}, 352: {}, 356: {},
360: {}, 364: {}, 368: {}, 376: {}, 388: {},
392: {}, 398: {}, 400: {}, 404: {}, 408: {},
410: {}, 414: {}, 417: {}, 418: {}, 422: {},
426: {}, 430: {}, 434: {}, 446: {}, 454: {},
458: {}, 462: {}, 480: {}, 484: {}, 496: {},
498: {}, 504: {}, 512: {}, 516: {}, 524: {},
532: {}, 533: {}, 548: {}, 554: {}, 558: {},
566: {}, 578: {}, 586: {}, 590: {}, 598: {},
600: {}, 604: {}, 608: {}, 634: {}, 643: {},
646: {}, 654: {}, 682: {}, 690: {}, 694: {},
702: {}, 704: {}, 706: {}, 710: {}, 728: {},
748: {}, 752: {}, 756: {}, 760: {}, 764: {},
776: {}, 780: {}, 784: {}, 788: {}, 800: {},
807: {}, 818: {}, 826: {}, 834: {}, 840: {},
858: {}, 860: {}, 882: {}, 886: {}, 901: {},
927: {}, 928: {}, 929: {}, 930: {}, 931: {},
932: {}, 933: {}, 934: {}, 936: {}, 938: {},
940: {}, 941: {}, 943: {}, 944: {}, 946: {},
947: {}, 948: {}, 949: {}, 950: {}, 951: {},
952: {}, 953: {}, 955: {}, 956: {}, 957: {},
958: {}, 959: {}, 960: {}, 961: {}, 962: {},
963: {}, 964: {}, 965: {}, 967: {}, 968: {},
969: {}, 970: {}, 971: {}, 972: {}, 973: {},
975: {}, 976: {}, 977: {}, 978: {}, 979: {},
980: {}, 981: {}, 984: {}, 985: {}, 986: {},
990: {}, 994: {}, 997: {}, 999: {},
170: {}, 174: {}, 188: {}, 192: {}, 203: {},
208: {}, 214: {}, 222: {}, 230: {}, 232: {},
238: {}, 242: {}, 262: {}, 270: {}, 292: {},
320: {}, 324: {}, 328: {}, 332: {}, 340: {},
344: {}, 348: {}, 352: {}, 356: {}, 360: {},
364: {}, 368: {}, 376: {}, 388: {}, 392: {},
398: {}, 400: {}, 404: {}, 408: {}, 410: {},
414: {}, 417: {}, 418: {}, 422: {}, 426: {},
430: {}, 434: {}, 446: {}, 454: {}, 458: {},
462: {}, 480: {}, 484: {}, 496: {}, 498: {},
504: {}, 512: {}, 516: {}, 524: {}, 532: {},
533: {}, 548: {}, 554: {}, 558: {}, 566: {},
578: {}, 586: {}, 590: {}, 598: {}, 600: {},
604: {}, 608: {}, 634: {}, 643: {}, 646: {},
654: {}, 682: {}, 690: {}, 702: {}, 704: {},
706: {}, 710: {}, 728: {}, 748: {}, 752: {},
756: {}, 760: {}, 764: {}, 776: {}, 780: {},
784: {}, 788: {}, 800: {}, 807: {}, 818: {},
826: {}, 834: {}, 840: {}, 858: {}, 860: {},
882: {}, 886: {}, 901: {}, 924: {}, 925: {},
926: {}, 927: {}, 928: {}, 929: {}, 930: {},
933: {}, 934: {}, 936: {}, 938: {}, 940: {},
941: {}, 943: {}, 944: {}, 946: {}, 947: {},
948: {}, 949: {}, 950: {}, 951: {}, 952: {},
953: {}, 955: {}, 956: {}, 957: {}, 958: {},
959: {}, 960: {}, 961: {}, 962: {}, 963: {},
964: {}, 965: {}, 967: {}, 968: {}, 969: {},
970: {}, 971: {}, 972: {}, 973: {}, 975: {},
976: {}, 977: {}, 978: {}, 979: {}, 980: {},
981: {}, 984: {}, 985: {}, 986: {}, 990: {},
994: {}, 997: {}, 999: {},
}
+35 -4
View File
@@ -201,6 +201,15 @@ only for the nil-values).
Usage: omitnil
# Omit Zero
Allows to skip the validation if the value is a zero value.
For pointers, it checks if the pointer is nil or the underlying value is a zero value.
For slices and maps, it checks if the value is nil or empty.
Otherwise, behaves the same as omitempty.
Usage: omitzero
# Dive
This tells the validator to dive into a slice, array or map and validate that
@@ -789,6 +798,12 @@ This validates that a string value contains ASCII alphanumeric characters only
Usage: alphanum
# Alphanumeric Space
This validates that a string value contains ASCII alphanumeric characters and spaces only
Usage: alphanumspace
# Alpha Unicode
This validates that a string value contains unicode alpha characters only
@@ -1263,6 +1278,15 @@ This validates that a string value contains a valid Unix Address.
Usage: unix_addr
# Unix Domain Socket Exists
This validates that a Unix domain socket file exists at the specified path.
It checks both filesystem-based sockets and Linux abstract sockets (prefixed with @).
For filesystem sockets, it verifies the path exists and is a socket file.
For abstract sockets on Linux, it checks /proc/net/unix.
Usage: uds_exists
# Media Access Control Address MAC
This validates that a string value contains a valid MAC Address.
@@ -1378,13 +1402,20 @@ More information on https://pkg.go.dev/golang.org/x/text/language
Usage: bcp47_language_tag
BIC (SWIFT code)
BIC (SWIFT code - 2022 standard)
This validates that a string value is a valid Business Identifier Code (SWIFT code), defined in ISO 9362.
More information on https://www.iso.org/standard/60390.html
This validates that a string value is a valid Business Identifier Code (SWIFT code), defined in ISO 9362:2022.
More information on https://www.iso.org/standard/84108.html
Usage: bic
BIC (SWIFT code - 2014 standard)
This validates that a string value is a valid Business Identifier Code (SWIFT code), defined in ISO 9362:2014.
More information on https://www.iso.org/standard/60390.html
Usage: bic_iso_9362_2014
# RFC 1035 label
This validates that a string value is a valid dns RFC 1035 label, defined in RFC 1035.
@@ -1519,7 +1550,7 @@ This package panics when bad input is provided, this is by design, bad code like
that should not make it to production.
type Test struct {
TestField string `validate:"nonexistantfunction=1"`
TestField string `validate:"nonexistentfunction=1"`
}
t := &Test{
+7 -3
View File
@@ -9,6 +9,7 @@ const (
alphaRegexString = "^[a-zA-Z]+$"
alphaSpaceRegexString = "^[a-zA-Z ]+$"
alphaNumericRegexString = "^[a-zA-Z0-9]+$"
alphaNumericSpaceRegexString = "^[a-zA-Z0-9 ]+$"
alphaUnicodeRegexString = "^[\\p{L}]+$"
alphaUnicodeNumericRegexString = "^[\\p{L}\\p{N}]+$"
numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$"
@@ -20,7 +21,7 @@ const (
hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$"
hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
e164RegexString = "^\\+[1-9]?[0-9]{7,14}$"
e164RegexString = "^\\+?[1-9]\\d{7,14}$"
base32RegexString = "^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=|[A-Z2-7]{8})$"
base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$"
@@ -68,7 +69,8 @@ const (
hTMLRegexString = `<[/]?([a-zA-Z]+).*?>`
jWTRegexString = "^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]*$"
splitParamsRegexString = `'[^']*'|\S+`
bicRegexString = `^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$`
bic2014RegexString = `^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$`
bic2022RegexString = `^[A-Z0-9]{4}[A-Z]{2}[A-Z0-9]{2}(?:[A-Z0-9]{3})?$`
semverRegexString = `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` // numbered capture groups https://semver.org/
dnsRegexStringRFC1035Label = "^[a-z]([-a-z0-9]*[a-z0-9])?$"
cveRegexString = `^CVE-(1999|2\d{3})-(0[^0]\d{2}|0\d[^0]\d{1}|0\d{2}[^0]|[1-9]{1}\d{3,})$` // CVE Format Id https://cve.mitre.org/cve/identifiers/syntaxchange.html
@@ -95,6 +97,7 @@ func lazyRegexCompile(str string) func() *regexp.Regexp {
var (
alphaRegex = lazyRegexCompile(alphaRegexString)
alphaSpaceRegex = lazyRegexCompile(alphaSpaceRegexString)
alphanNumericSpaceRegex = lazyRegexCompile(alphaNumericSpaceRegexString)
alphaNumericRegex = lazyRegexCompile(alphaNumericRegexString)
alphaUnicodeRegex = lazyRegexCompile(alphaUnicodeRegexString)
alphaUnicodeNumericRegex = lazyRegexCompile(alphaUnicodeNumericRegexString)
@@ -153,7 +156,8 @@ var (
hTMLRegex = lazyRegexCompile(hTMLRegexString)
jWTRegex = lazyRegexCompile(jWTRegexString)
splitParamsRegex = lazyRegexCompile(splitParamsRegexString)
bicRegex = lazyRegexCompile(bicRegexString)
bic2014Regex = lazyRegexCompile(bic2014RegexString)
bic2022Regex = lazyRegexCompile(bic2022RegexString)
semverRegex = lazyRegexCompile(semverRegexString)
dnsRegexRFC1035Label = lazyRegexCompile(dnsRegexStringRFC1035Label)
cveRegex = lazyRegexCompile(cveRegexString)
+20
View File
@@ -1074,6 +1074,26 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
translation: "{0} can only contain alphanumeric characters",
override: false,
},
{
tag: "alphaspace",
translation: "{0} can only contain alphabetic and space characters",
override: false,
},
{
tag: "alphanumspace",
translation: "{0} can only contain alphanumeric and space characters",
override: false,
},
{
tag: "alphaunicode",
translation: "{0} can only contain unicode alphabetic characters",
override: false,
},
{
tag: "alphanumunicode",
translation: "{0} can only contain unicode alphanumeric characters",
override: false,
},
{
tag: "numeric",
translation: "{0} must be a valid numeric value",
+3 -1
View File
@@ -214,7 +214,9 @@ BEGIN:
}
// if got here there was more namespace, cannot go any deeper
panic("Invalid field namespace")
// return found=false instead of panicking to handle cases like ValidateMap
// where cross-field validators (required_if, etc.) can't navigate non-struct parents
return
}
// asInt returns the parameter as an int64
+16 -1
View File
@@ -27,6 +27,16 @@ Use the links above for more information on each.
# changelog
* Oct 20, 2025 - [1.18.1](https://github.com/klauspost/compress/releases/tag/v1.18.1)
* zstd: Add simple zstd EncodeTo/DecodeTo functions https://github.com/klauspost/compress/pull/1079
* zstd: Fix incorrect buffer size in dictionary encodes https://github.com/klauspost/compress/pull/1059
* s2: check for cap, not len of buffer in EncodeBetter/Best by @vdarulis in https://github.com/klauspost/compress/pull/1080
* zlib: Avoiding extra allocation in zlib.reader.Reset by @travelpolicy in https://github.com/klauspost/compress/pull/1086
* gzhttp: remove redundant err check in zstdReader by @ryanfowler in https://github.com/klauspost/compress/pull/1090
* flate: Faster load+store https://github.com/klauspost/compress/pull/1104
* flate: Simplify matchlen https://github.com/klauspost/compress/pull/1101
* flate: Use exact sizes for huffman tables https://github.com/klauspost/compress/pull/1103
* Feb 19th, 2025 - [1.18.0](https://github.com/klauspost/compress/releases/tag/v1.18.0)
* Add unsafe little endian loaders https://github.com/klauspost/compress/pull/1036
* fix: check `r.err != nil` but return a nil value error `err` by @alingse in https://github.com/klauspost/compress/pull/1028
@@ -36,6 +46,9 @@ Use the links above for more information on each.
* flate: Fix matchlen L5+L6 https://github.com/klauspost/compress/pull/1049
* flate: Cleanup & reduce casts https://github.com/klauspost/compress/pull/1050
<details>
<summary>See changes to v1.17.x</summary>
* Oct 11th, 2024 - [1.17.11](https://github.com/klauspost/compress/releases/tag/v1.17.11)
* zstd: Fix extra CRC written with multiple Close calls https://github.com/klauspost/compress/pull/1017
* s2: Don't use stack for index tables https://github.com/klauspost/compress/pull/1014
@@ -102,7 +115,8 @@ https://github.com/klauspost/compress/pull/919 https://github.com/klauspost/comp
* s2: Do 2 overlapping match checks https://github.com/klauspost/compress/pull/839
* flate: Add amd64 assembly matchlen https://github.com/klauspost/compress/pull/837
* gzip: Copy bufio.Reader on Reset by @thatguystone in https://github.com/klauspost/compress/pull/860
</details>
<details>
<summary>See changes to v1.16.x</summary>
@@ -669,3 +683,4 @@ Here are other packages of good quality and pure Go (no cgo wrappers or autoconv
# license
This code is licensed under the same conditions as the original Go code. See LICENSE file.
+3 -1
View File
@@ -421,7 +421,9 @@ func (d *compressor) deflateLazy() {
d.h = newHuffmanEncoder(maxFlateBlockTokens)
}
var tmp [256]uint16
for _, v := range d.window[s.index:d.windowEnd] {
toIndex := d.window[s.index:d.windowEnd]
toIndex = toIndex[:min(len(toIndex), maxFlateBlockTokens)]
for _, v := range toIndex {
tmp[v]++
}
d.h.generate(tmp[:], 15)
+5 -4
View File
@@ -646,7 +646,7 @@ func (w *huffmanBitWriter) writeBlockDynamic(tokens *tokens, eof bool, input []b
w.lastHeader = 0
}
numLiterals, numOffsets := w.indexTokens(tokens, fillReuse && !sync)
numLiterals, numOffsets := w.indexTokens(tokens, true)
extraBits := 0
ssize, storable := w.storedSize(input)
@@ -781,7 +781,7 @@ func (w *huffmanBitWriter) fillTokens() {
// literalFreq and offsetFreq, and generates literalEncoding
// and offsetEncoding.
// The number of literal and offset tokens is returned.
func (w *huffmanBitWriter) indexTokens(t *tokens, filled bool) (numLiterals, numOffsets int) {
func (w *huffmanBitWriter) indexTokens(t *tokens, alwaysEOB bool) (numLiterals, numOffsets int) {
//copy(w.literalFreq[:], t.litHist[:])
*(*[256]uint16)(w.literalFreq[:]) = t.litHist
//copy(w.literalFreq[256:], t.extraHist[:])
@@ -791,9 +791,10 @@ func (w *huffmanBitWriter) indexTokens(t *tokens, filled bool) (numLiterals, num
if t.n == 0 {
return
}
if filled {
return maxNumLit, maxNumDist
if alwaysEOB {
w.literalFreq[endBlockMarker] = 1
}
// get the number of literals
numLiterals = len(w.literalFreq)
for w.literalFreq[numLiterals-1] == 0 {
+16 -4
View File
@@ -61,13 +61,19 @@ var bitWriterPool = sync.Pool{
},
}
// tokensPool contains tokens struct objects that can be reused
var tokensPool = sync.Pool{
New: func() any {
return &tokens{}
},
}
// StatelessDeflate allows compressing directly to a Writer without retaining state.
// When returning everything will be flushed.
// Up to 8KB of an optional dictionary can be given which is presumed to precede the block.
// Longer dictionaries will be truncated and will still produce valid output.
// Sending nil dictionary is perfectly fine.
func StatelessDeflate(out io.Writer, in []byte, eof bool, dict []byte) error {
var dst tokens
bw := bitWriterPool.Get().(*huffmanBitWriter)
bw.reset(out)
defer func() {
@@ -91,6 +97,12 @@ func StatelessDeflate(out io.Writer, in []byte, eof bool, dict []byte) error {
// For subsequent loops, keep shallow dict reference to avoid alloc+copy.
var inDict []byte
dst := tokensPool.Get().(*tokens)
dst.Reset()
defer func() {
tokensPool.Put(dst)
}()
for len(in) > 0 {
todo := in
if len(inDict) > 0 {
@@ -113,9 +125,9 @@ func StatelessDeflate(out io.Writer, in []byte, eof bool, dict []byte) error {
}
// Compress
if len(inDict) == 0 {
statelessEnc(&dst, todo, int16(len(dict)))
statelessEnc(dst, todo, int16(len(dict)))
} else {
statelessEnc(&dst, inDict[:maxStatelessDict+len(todo)], maxStatelessDict)
statelessEnc(dst, inDict[:maxStatelessDict+len(todo)], maxStatelessDict)
}
isEof := eof && len(in) == 0
@@ -129,7 +141,7 @@ func StatelessDeflate(out io.Writer, in []byte, eof bool, dict []byte) error {
// If we removed less than 1/16th, huffman compress the block.
bw.writeBlockHuff(isEof, uncompressed, len(in) == 0)
} else {
bw.writeBlockDynamic(&dst, isEof, uncompressed, len(in) == 0)
bw.writeBlockDynamic(dst, isEof, uncompressed, len(in) == 0)
}
if len(in) > 0 {
// Retain a dict if we have more
+4361 -1352
View File
File diff suppressed because it is too large Load Diff
+304 -111
View File
@@ -147,9 +147,12 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.50.4"
#define SQLITE_VERSION_NUMBER 3050004
#define SQLITE_SOURCE_ID "2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3"
#define SQLITE_VERSION "3.51.1"
#define SQLITE_VERSION_NUMBER 3051001
#define SQLITE_SOURCE_ID "2025-11-28 17:28:25 281fc0e9afc38674b9b0991943b9e9d1e64c6cbdb133d35f6f5c87ff6af38a88"
#define SQLITE_SCM_BRANCH "branch-3.51"
#define SQLITE_SCM_TAGS "release version-3.51.1"
#define SQLITE_SCM_DATETIME "2025-11-28T17:28:25.933Z"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -169,9 +172,9 @@ extern "C" {
** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 );
** </pre></blockquote>)^
**
** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION]
** macro. ^The sqlite3_libversion() function returns a pointer to the
** to the sqlite3_version[] string constant. The sqlite3_libversion()
** ^The sqlite3_version[] string constant contains the text of the
** [SQLITE_VERSION] macro. ^The sqlite3_libversion() function returns a
** pointer to the sqlite3_version[] string constant. The sqlite3_libversion()
** function is provided for use in DLLs since DLL users usually do not have
** direct access to string constants within the DLL. ^The
** sqlite3_libversion_number() function returns an integer equal to
@@ -371,7 +374,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
** without having to use a lot of C code.
**
** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded,
** semicolon-separate SQL statements passed into its 2nd argument,
** semicolon-separated SQL statements passed into its 2nd argument,
** in the context of the [database connection] passed in as its 1st
** argument. ^If the callback function of the 3rd argument to
** sqlite3_exec() is not NULL, then it is invoked for each result row
@@ -404,7 +407,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
** result row is NULL then the corresponding string pointer for the
** sqlite3_exec() callback is a NULL pointer. ^The 4th argument to the
** sqlite3_exec() callback is an array of pointers to strings where each
** entry represents the name of corresponding result column as obtained
** entry represents the name of a corresponding result column as obtained
** from [sqlite3_column_name()].
**
** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer
@@ -498,6 +501,9 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8))
#define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8))
#define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8))
#define SQLITE_ERROR_RESERVESIZE (SQLITE_ERROR | (4<<8))
#define SQLITE_ERROR_KEY (SQLITE_ERROR | (5<<8))
#define SQLITE_ERROR_UNABLE (SQLITE_ERROR | (6<<8))
#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8))
#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8))
#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8))
@@ -532,6 +538,8 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8))
#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8))
#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8))
#define SQLITE_IOERR_BADKEY (SQLITE_IOERR | (35<<8))
#define SQLITE_IOERR_CODEC (SQLITE_IOERR | (36<<8))
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
@@ -590,7 +598,7 @@ SQLITE_API int sqlite3_exec(
** Note in particular that passing the SQLITE_OPEN_EXCLUSIVE flag into
** [sqlite3_open_v2()] does *not* cause the underlying database file
** to be opened using O_EXCL. Passing SQLITE_OPEN_EXCLUSIVE into
** [sqlite3_open_v2()] has historically be a no-op and might become an
** [sqlite3_open_v2()] has historically been a no-op and might become an
** error in future versions of SQLite.
*/
#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */
@@ -684,7 +692,7 @@ SQLITE_API int sqlite3_exec(
** SQLite uses one of these integer values as the second
** argument to calls it makes to the xLock() and xUnlock() methods
** of an [sqlite3_io_methods] object. These values are ordered from
** lest restrictive to most restrictive.
** least restrictive to most restrictive.
**
** The argument to xLock() is always SHARED or higher. The argument to
** xUnlock is either SHARED or NONE.
@@ -925,7 +933,7 @@ struct sqlite3_io_methods {
** connection. See also [SQLITE_FCNTL_FILE_POINTER].
**
** <li>[[SQLITE_FCNTL_SYNC_OMITTED]]
** No longer in use.
** The SQLITE_FCNTL_SYNC_OMITTED file-control is no longer used.
**
** <li>[[SQLITE_FCNTL_SYNC]]
** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and
@@ -1000,7 +1008,7 @@ struct sqlite3_io_methods {
**
** <li>[[SQLITE_FCNTL_VFSNAME]]
** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of
** all [VFSes] in the VFS stack. The names are of all VFS shims and the
** all [VFSes] in the VFS stack. The names of all VFS shims and the
** final bottom-level VFS are written into memory obtained from
** [sqlite3_malloc()] and the result is stored in the char* variable
** that the fourth parameter of [sqlite3_file_control()] points to.
@@ -1014,7 +1022,7 @@ struct sqlite3_io_methods {
** ^The [SQLITE_FCNTL_VFS_POINTER] opcode finds a pointer to the top-level
** [VFSes] currently in use. ^(The argument X in
** sqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be
** of type "[sqlite3_vfs] **". This opcodes will set *X
** of type "[sqlite3_vfs] **". This opcode will set *X
** to a pointer to the top-level VFS.)^
** ^When there are multiple VFS shims in the stack, this opcode finds the
** upper-most shim only.
@@ -1204,7 +1212,7 @@ struct sqlite3_io_methods {
** <li>[[SQLITE_FCNTL_EXTERNAL_READER]]
** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect
** whether or not there is a database client in another process with a wal-mode
** transaction open on the database or not. It is only available on unix.The
** transaction open on the database or not. It is only available on unix. The
** (void*) argument passed with this file-control should be a pointer to a
** value of type (int). The integer value is set to 1 if the database is a wal
** mode database and there exists at least one client in another process that
@@ -1222,6 +1230,15 @@ struct sqlite3_io_methods {
** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control
** purges the contents of the in-memory page cache. If there is an open
** transaction, or if the db is a temp-db, this opcode is a no-op, not an error.
**
** <li>[[SQLITE_FCNTL_FILESTAT]]
** The [SQLITE_FCNTL_FILESTAT] opcode returns low-level diagnostic information
** about the [sqlite3_file] objects used access the database and journal files
** for the given schema. The fourth parameter to [sqlite3_file_control()]
** should be an initialized [sqlite3_str] pointer. JSON text describing
** various aspects of the sqlite3_file object is appended to the sqlite3_str.
** The SQLITE_FCNTL_FILESTAT opcode is usually a no-op, unless compile-time
** options are used to enable it.
** </ul>
*/
#define SQLITE_FCNTL_LOCKSTATE 1
@@ -1267,6 +1284,7 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_RESET_CACHE 42
#define SQLITE_FCNTL_NULL_IO 43
#define SQLITE_FCNTL_BLOCK_ON_CONNECT 44
#define SQLITE_FCNTL_FILESTAT 45
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@@ -1629,7 +1647,7 @@ struct sqlite3_vfs {
** SQLite interfaces so that an application usually does not need to
** invoke sqlite3_initialize() directly. For example, [sqlite3_open()]
** calls sqlite3_initialize() so the SQLite library will be automatically
** initialized when [sqlite3_open()] is called if it has not be initialized
** initialized when [sqlite3_open()] is called if it has not been initialized
** already. ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT]
** compile-time option, then the automatic calls to sqlite3_initialize()
** are omitted and the application must call sqlite3_initialize() directly
@@ -1886,21 +1904,21 @@ struct sqlite3_mem_methods {
** The [sqlite3_mem_methods]
** structure is filled with the currently defined memory allocation routines.)^
** This option can be used to overload the default memory allocation
** routines with a wrapper that simulations memory allocation failure or
** routines with a wrapper that simulates memory allocation failure or
** tracks memory usage, for example. </dd>
**
** [[SQLITE_CONFIG_SMALL_MALLOC]] <dt>SQLITE_CONFIG_SMALL_MALLOC</dt>
** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of
** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes a single argument of
** type int, interpreted as a boolean, which if true provides a hint to
** SQLite that it should avoid large memory allocations if possible.
** SQLite will run faster if it is free to make large memory allocations,
** but some application might prefer to run slower in exchange for
** but some applications might prefer to run slower in exchange for
** guarantees about memory fragmentation that are possible if large
** allocations are avoided. This hint is normally off.
** </dd>
**
** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt>
** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int,
** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes a single argument of type int,
** interpreted as a boolean, which enables or disables the collection of
** memory allocation statistics. ^(When memory allocation statistics are
** disabled, the following SQLite interfaces become non-operational:
@@ -1945,7 +1963,7 @@ struct sqlite3_mem_methods {
** ^If pMem is NULL and N is non-zero, then each database connection
** does an initial bulk allocation for page cache memory
** from [sqlite3_malloc()] sufficient for N cache lines if N is positive or
** of -1024*N bytes if N is negative, . ^If additional
** of -1024*N bytes if N is negative. ^If additional
** page cache memory is needed beyond what is provided by the initial
** allocation, then SQLite goes to [sqlite3_malloc()] separately for each
** additional cache line. </dd>
@@ -1974,7 +1992,7 @@ struct sqlite3_mem_methods {
** <dd> ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a
** pointer to an instance of the [sqlite3_mutex_methods] structure.
** The argument specifies alternative low-level mutex routines to be used
** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of
** in place of the mutex routines built into SQLite.)^ ^SQLite makes a copy of
** the content of the [sqlite3_mutex_methods] structure before the call to
** [sqlite3_config()] returns. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
@@ -2016,7 +2034,7 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which
** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of
** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies off
** the current page cache implementation into that object.)^ </dd>
**
** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt>
@@ -2033,7 +2051,7 @@ struct sqlite3_mem_methods {
** the logger function is a copy of the first parameter to the corresponding
** [sqlite3_log()] call and is intended to be a [result code] or an
** [extended result code]. ^The third parameter passed to the logger is
** log message after formatting via [sqlite3_snprintf()].
** a log message after formatting via [sqlite3_snprintf()].
** The SQLite logging interface is not reentrant; the logger function
** supplied by the application must not invoke any SQLite interface.
** In a multi-threaded application, the application-defined logger
@@ -2224,7 +2242,7 @@ struct sqlite3_mem_methods {
** These constants are the available integer configuration options that
** can be passed as the second parameter to the [sqlite3_db_config()] interface.
**
** The [sqlite3_db_config()] interface is a var-args functions. It takes a
** The [sqlite3_db_config()] interface is a var-args function. It takes a
** variable number of parameters, though always at least two. The number of
** parameters passed into sqlite3_db_config() depends on which of these
** constants is given as the second parameter. This documentation page
@@ -2336,17 +2354,20 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
** <dd> ^This option is used to enable or disable the
** [fts3_tokenizer()] function which is part of the
** [FTS3] full-text search engine extension.
** There must be two additional arguments.
** The first argument is an integer which is 0 to disable fts3_tokenizer() or
** positive to enable fts3_tokenizer() or negative to leave the setting
** unchanged.
** The second parameter is a pointer to an integer into which
** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled
** following this call. The second parameter may be a NULL pointer, in
** which case the new setting is not reported back. </dd>
** <dd> ^This option is used to enable or disable using the
** [fts3_tokenizer()] function - part of the [FTS3] full-text search engine
** extension - without using bound parameters as the parameters. Doing so
** is disabled by default. There must be two additional arguments. The first
** argument is an integer. If it is passed 0, then using fts3_tokenizer()
** without bound parameters is disabled. If it is passed a positive value,
** then calling fts3_tokenizer without bound parameters is enabled. If it
** is passed a negative value, this setting is not modified - this can be
** used to query for the current setting. The second parameter is a pointer
** to an integer into which is written 0 or 1 to indicate the current value
** of this setting (after it is modified, if applicable). The second
** parameter may be a NULL pointer, in which case the value of the setting
** is not reported back. Refer to [FTS3] documentation for further details.
** </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]]
** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt>
@@ -2358,8 +2379,8 @@ struct sqlite3_mem_methods {
** When the first argument to this interface is 1, then only the C-API is
** enabled and the SQL function remains disabled. If the first argument to
** this interface is 0, then both the C-API and the SQL function are disabled.
** If the first argument is -1, then no changes are made to state of either the
** C-API or the SQL function.
** If the first argument is -1, then no changes are made to the state of either
** the C-API or the SQL function.
** The second parameter is a pointer to an integer into which
** is written 0 or 1 to indicate whether [sqlite3_load_extension()] interface
** is disabled or enabled following this call. The second parameter may
@@ -2477,7 +2498,7 @@ struct sqlite3_mem_methods {
** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]]
** <dt>SQLITE_DBCONFIG_LEGACY_ALTER_TABLE</dt>
** <dd>The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates
** the legacy behavior of the [ALTER TABLE RENAME] command such it
** the legacy behavior of the [ALTER TABLE RENAME] command such that it
** behaves as it did prior to [version 3.24.0] (2018-06-04). See the
** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for
** additional information. This feature can also be turned on and off
@@ -2526,7 +2547,7 @@ struct sqlite3_mem_methods {
** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</dt>
** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates
** the legacy file format flag. When activated, this flag causes all newly
** created database file to have a schema format version number (the 4-byte
** created database files to have a schema format version number (the 4-byte
** integer found at offset 44 into the database header) of 1. This in turn
** means that the resulting database file will be readable and writable by
** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting,
@@ -2553,7 +2574,7 @@ struct sqlite3_mem_methods {
** the database handle both when the SQL statement is prepared and when it
** is stepped. The flag is set (collection of statistics is enabled)
** by default. <p>This option takes two arguments: an integer and a pointer to
** an integer.. The first argument is 1, 0, or -1 to enable, disable, or
** an integer. The first argument is 1, 0, or -1 to enable, disable, or
** leave unchanged the statement scanstatus option. If the second argument
** is not NULL, then the value of the statement scanstatus setting after
** processing the first argument is written into the integer that the second
@@ -2596,8 +2617,8 @@ struct sqlite3_mem_methods {
** <dd>The SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE option enables or disables the
** ability of the [ATTACH DATABASE] SQL command to open a database for writing.
** This capability is enabled by default. Applications can disable or
** reenable this capability using the current DBCONFIG option. If the
** the this capability is disabled, the [ATTACH] command will still work,
** reenable this capability using the current DBCONFIG option. If
** this capability is disabled, the [ATTACH] command will still work,
** but the database will be opened read-only. If this option is disabled,
** then the ability to create a new database using [ATTACH] is also disabled,
** regardless of the value of the [SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]
@@ -2631,7 +2652,7 @@ struct sqlite3_mem_methods {
**
** <p>Most of the SQLITE_DBCONFIG options take two arguments, so that the
** overall call to [sqlite3_db_config()] has a total of four parameters.
** The first argument (the third parameter to sqlite3_db_config()) is a integer.
** The first argument (the third parameter to sqlite3_db_config()) is an integer.
** The second argument is a pointer to an integer. If the first argument is 1,
** then the option becomes enabled. If the first integer argument is 0, then the
** option is disabled. If the first argument is -1, then the option setting
@@ -2921,7 +2942,7 @@ SQLITE_API int sqlite3_is_interrupted(sqlite3*);
** ^These routines return 0 if the statement is incomplete. ^If a
** memory allocation fails, then SQLITE_NOMEM is returned.
**
** ^These routines do not parse the SQL statements thus
** ^These routines do not parse the SQL statements and thus
** will not detect syntactically incorrect SQL.
**
** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior
@@ -3038,7 +3059,7 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
** indefinitely if possible. The results of passing any other negative value
** are undefined.
**
** Internally, each SQLite database handle store two timeout values - the
** Internally, each SQLite database handle stores two timeout values - the
** busy-timeout (used for rollback mode databases, or if the VFS does not
** support blocking locks) and the setlk-timeout (used for blocking locks
** on wal-mode databases). The sqlite3_busy_timeout() method sets both
@@ -3068,7 +3089,7 @@ SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags);
** This is a legacy interface that is preserved for backwards compatibility.
** Use of this interface is not recommended.
**
** Definition: A <b>result table</b> is memory data structure created by the
** Definition: A <b>result table</b> is a memory data structure created by the
** [sqlite3_get_table()] interface. A result table records the
** complete query results from one or more queries.
**
@@ -3211,7 +3232,7 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
** ^Calling sqlite3_free() with a pointer previously returned
** by sqlite3_malloc() or sqlite3_realloc() releases that memory so
** that it might be reused. ^The sqlite3_free() routine is
** a no-op if is called with a NULL pointer. Passing a NULL pointer
** a no-op if it is called with a NULL pointer. Passing a NULL pointer
** to sqlite3_free() is harmless. After being freed, memory
** should neither be read nor written. Even reading previously freed
** memory might result in a segmentation fault or other severe error.
@@ -3229,13 +3250,13 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
** sqlite3_free(X).
** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation
** of at least N bytes in size or NULL if insufficient memory is available.
** ^If M is the size of the prior allocation, then min(N,M) bytes
** of the prior allocation are copied into the beginning of buffer returned
** ^If M is the size of the prior allocation, then min(N,M) bytes of the
** prior allocation are copied into the beginning of the buffer returned
** by sqlite3_realloc(X,N) and the prior allocation is freed.
** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the
** prior allocation is not freed.
**
** ^The sqlite3_realloc64(X,N) interfaces works the same as
** ^The sqlite3_realloc64(X,N) interface works the same as
** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead
** of a 32-bit signed integer.
**
@@ -3285,7 +3306,7 @@ SQLITE_API sqlite3_uint64 sqlite3_msize(void*);
** was last reset. ^The values returned by [sqlite3_memory_used()] and
** [sqlite3_memory_highwater()] include any overhead
** added by SQLite in its implementation of [sqlite3_malloc()],
** but not overhead added by the any underlying system library
** but not overhead added by any underlying system library
** routines that [sqlite3_malloc()] may call.
**
** ^The memory high-water mark is reset to the current value of
@@ -3737,7 +3758,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** there is no harm in trying.)
**
** ^(<dt>[SQLITE_OPEN_SHAREDCACHE]</dt>
** <dd>The database is opened [shared cache] enabled, overriding
** <dd>The database is opened with [shared cache] enabled, overriding
** the default shared cache setting provided by
** [sqlite3_enable_shared_cache()].)^
** The [use of shared cache mode is discouraged] and hence shared cache
@@ -3745,7 +3766,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** this option is a no-op.
**
** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt>
** <dd>The database is opened [shared cache] disabled, overriding
** <dd>The database is opened with [shared cache] disabled, overriding
** the default shared cache setting provided by
** [sqlite3_enable_shared_cache()].)^
**
@@ -4163,7 +4184,7 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename);
** subsequent calls to other SQLite interface functions.)^
**
** ^The sqlite3_errstr(E) interface returns the English-language text
** that describes the [result code] E, as UTF-8, or NULL if E is not an
** that describes the [result code] E, as UTF-8, or NULL if E is not a
** result code for which a text error message is available.
** ^(Memory to hold the error message string is managed internally
** and must not be freed by the application)^.
@@ -4171,7 +4192,7 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename);
** ^If the most recent error references a specific token in the input
** SQL, the sqlite3_error_offset() interface returns the byte offset
** of the start of that token. ^The byte offset returned by
** sqlite3_error_offset() assumes that the input SQL is UTF8.
** sqlite3_error_offset() assumes that the input SQL is UTF-8.
** ^If the most recent error does not reference a specific token in the input
** SQL, then the sqlite3_error_offset() function returns -1.
**
@@ -4196,6 +4217,34 @@ SQLITE_API const void *sqlite3_errmsg16(sqlite3*);
SQLITE_API const char *sqlite3_errstr(int);
SQLITE_API int sqlite3_error_offset(sqlite3 *db);
/*
** CAPI3REF: Set Error Codes And Message
** METHOD: sqlite3
**
** Set the error code of the database handle passed as the first argument
** to errcode, and the error message to a copy of nul-terminated string
** zErrMsg. If zErrMsg is passed NULL, then the error message is set to
** the default message associated with the supplied error code. Subsequent
** calls to [sqlite3_errcode()] and [sqlite3_errmsg()] and similar will
** return the values set by this routine in place of what was previously
** set by SQLite itself.
**
** This function returns SQLITE_OK if the error code and error message are
** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if
** the database handle is NULL or invalid.
**
** The error code and message set by this routine remains in effect until
** they are changed, either by another call to this routine or until they are
** changed to by SQLite itself to reflect the result of some subsquent
** API call.
**
** This function is intended for use by SQLite extensions or wrappers. The
** idea is that an extension or wrapper can use this routine to set error
** messages and error codes and thus behave more like a core SQLite
** feature from the point of view of an application.
*/
SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg);
/*
** CAPI3REF: Prepared Statement Object
** KEYWORDS: {prepared statement} {prepared statements}
@@ -4270,8 +4319,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
**
** These constants define various performance limits
** that can be lowered at run-time using [sqlite3_limit()].
** The synopsis of the meanings of the various limits is shown below.
** Additional information is available at [limits | Limits in SQLite].
** A concise description of these limits follows, and additional information
** is available at [limits | Limits in SQLite].
**
** <dl>
** [[SQLITE_LIMIT_LENGTH]] ^(<dt>SQLITE_LIMIT_LENGTH</dt>
@@ -4336,7 +4385,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
/*
** CAPI3REF: Prepare Flags
**
** These constants define various flags that can be passed into
** These constants define various flags that can be passed into the
** "prepFlags" parameter of the [sqlite3_prepare_v3()] and
** [sqlite3_prepare16_v3()] interfaces.
**
@@ -4423,7 +4472,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
** there is a small performance advantage to passing an nByte parameter that
** is the number of bytes in the input string <i>including</i>
** the nul-terminator.
** Note that nByte measure the length of the input in bytes, not
** Note that nByte measures the length of the input in bytes, not
** characters, even for the UTF-16 interfaces.
**
** ^If pzTail is not NULL then *pzTail is made to point to the first byte
@@ -4557,7 +4606,7 @@ SQLITE_API int sqlite3_prepare16_v3(
**
** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory
** is available to hold the result, or if the result would exceed the
** the maximum string length determined by the [SQLITE_LIMIT_LENGTH].
** maximum string length determined by the [SQLITE_LIMIT_LENGTH].
**
** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of
** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time
@@ -4745,7 +4794,7 @@ typedef struct sqlite3_value sqlite3_value;
**
** The context in which an SQL function executes is stored in an
** sqlite3_context object. ^A pointer to an sqlite3_context object
** is always first parameter to [application-defined SQL functions].
** is always the first parameter to [application-defined SQL functions].
** The application-defined SQL function implementation will pass this
** pointer through into calls to [sqlite3_result_int | sqlite3_result()],
** [sqlite3_aggregate_context()], [sqlite3_user_data()],
@@ -4869,9 +4918,11 @@ typedef struct sqlite3_context sqlite3_context;
** associated with the pointer P of type T. ^D is either a NULL pointer or
** a pointer to a destructor function for P. ^SQLite will invoke the
** destructor D with a single argument of P when it is finished using
** P. The T parameter should be a static string, preferably a string
** literal. The sqlite3_bind_pointer() routine is part of the
** [pointer passing interface] added for SQLite 3.20.0.
** P, even if the call to sqlite3_bind_pointer() fails. Due to a
** historical design quirk, results are undefined if D is
** SQLITE_TRANSIENT. The T parameter should be a static string,
** preferably a string literal. The sqlite3_bind_pointer() routine is
** part of the [pointer passing interface] added for SQLite 3.20.0.
**
** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer
** for the [prepared statement] or with a prepared statement for which
@@ -5482,7 +5533,7 @@ SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol);
**
** ^The sqlite3_finalize() function is called to delete a [prepared statement].
** ^If the most recent evaluation of the statement encountered no errors
** or if the statement is never been evaluated, then sqlite3_finalize() returns
** or if the statement has never been evaluated, then sqlite3_finalize() returns
** SQLITE_OK. ^If the most recent evaluation of statement S failed, then
** sqlite3_finalize(S) returns the appropriate [error code] or
** [extended error code].
@@ -5714,7 +5765,7 @@ SQLITE_API int sqlite3_create_window_function(
/*
** CAPI3REF: Text Encodings
**
** These constant define integer codes that represent the various
** These constants define integer codes that represent the various
** text encodings supported by SQLite.
*/
#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */
@@ -5806,7 +5857,7 @@ SQLITE_API int sqlite3_create_window_function(
** result.
** Every function that invokes [sqlite3_result_subtype()] should have this
** property. If it does not, then the call to [sqlite3_result_subtype()]
** might become a no-op if the function is used as term in an
** might become a no-op if the function is used as a term in an
** [expression index]. On the other hand, SQL functions that never invoke
** [sqlite3_result_subtype()] should avoid setting this property, as the
** purpose of this property is to disable certain optimizations that are
@@ -5933,7 +5984,7 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6
** sqlite3_value_nochange(X) interface returns true if and only if
** the column corresponding to X is unchanged by the UPDATE operation
** that the xUpdate method call was invoked to implement and if
** and the prior [xColumn] method call that was invoked to extracted
** the prior [xColumn] method call that was invoked to extract
** the value for that column returned without setting a result (probably
** because it queried [sqlite3_vtab_nochange()] and found that the column
** was unchanging). ^Within an [xUpdate] method, any value for which
@@ -6206,6 +6257,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi
** or a NULL pointer if there were no prior calls to
** sqlite3_set_clientdata() with the same values of D and N.
** Names are compared using strcmp() and are thus case sensitive.
** It returns 0 on success and SQLITE_NOMEM on allocation failure.
**
** If P and X are both non-NULL, then the destructor X is invoked with
** argument P on the first of the following occurrences:
@@ -8882,9 +8934,18 @@ SQLITE_API int sqlite3_status64(
** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a
** non-zero [error code] on failure.
**
** ^The sqlite3_db_status64(D,O,C,H,R) routine works exactly the same
** way as sqlite3_db_status(D,O,C,H,R) routine except that the C and H
** parameters are pointer to 64-bit integers (type: sqlite3_int64) instead
** of pointers to 32-bit integers, which allows larger status values
** to be returned. If a status value exceeds 2,147,483,647 then
** sqlite3_db_status() will truncate the value whereas sqlite3_db_status64()
** will return the full value.
**
** See also: [sqlite3_status()] and [sqlite3_stmt_status()].
*/
SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
SQLITE_API int sqlite3_db_status64(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int);
/*
** CAPI3REF: Status Parameters for database connections
@@ -8981,6 +9042,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** If an IO or other error occurs while writing a page to disk, the effect
** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The
** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0.
** <p>
** ^(There is overlap between the quantities measured by this parameter
** (SQLITE_DBSTATUS_CACHE_WRITE) and SQLITE_DBSTATUS_TEMPBUF_SPILL.
** Resetting one will reduce the other.)^
** </dd>
**
** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt>
@@ -8996,6 +9061,18 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** <dd>This parameter returns zero for the current value if and only if
** all foreign key constraints (deferred or immediate) have been
** resolved.)^ ^The highwater mark is always 0.
**
** [[SQLITE_DBSTATUS_TEMPBUF_SPILL] ^(<dt>SQLITE_DBSTATUS_TEMPBUF_SPILL</dt>
** <dd>^(This parameter returns the number of bytes written to temporary
** files on disk that could have been kept in memory had sufficient memory
** been available. This value includes writes to intermediate tables that
** are part of complex queries, external sorts that spill to disk, and
** writes to TEMP tables.)^
** ^The highwater mark is always 0.
** <p>
** ^(There is overlap between the quantities measured by this parameter
** (SQLITE_DBSTATUS_TEMPBUF_SPILL) and SQLITE_DBSTATUS_CACHE_WRITE.
** Resetting one will reduce the other.)^
** </dd>
** </dl>
*/
@@ -9012,7 +9089,8 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
#define SQLITE_DBSTATUS_DEFERRED_FKS 10
#define SQLITE_DBSTATUS_CACHE_USED_SHARED 11
#define SQLITE_DBSTATUS_CACHE_SPILL 12
#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */
#define SQLITE_DBSTATUS_TEMPBUF_SPILL 13
#define SQLITE_DBSTATUS_MAX 13 /* Largest defined DBSTATUS */
/*
@@ -9777,7 +9855,7 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
** is the number of pages currently in the write-ahead log file,
** including those that were just committed.
**
** The callback function should normally return [SQLITE_OK]. ^If an error
** ^The callback function should normally return [SQLITE_OK]. ^If an error
** code is returned, that error will propagate back up through the
** SQLite code base to cause the statement that provoked the callback
** to report an error, though the commit will have still occurred. If the
@@ -9785,13 +9863,26 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
** that does not correspond to any valid SQLite error code, the results
** are undefined.
**
** A single database handle may have at most a single write-ahead log callback
** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any
** previously registered write-ahead log callback. ^The return value is
** a copy of the third parameter from the previous call, if any, or 0.
** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the
** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will
** overwrite any prior [sqlite3_wal_hook()] settings.
** ^A single database handle may have at most a single write-ahead log
** callback registered at one time. ^Calling [sqlite3_wal_hook()]
** replaces the default behavior or previously registered write-ahead
** log callback.
**
** ^The return value is a copy of the third parameter from the
** previous call, if any, or 0.
**
** ^The [sqlite3_wal_autocheckpoint()] interface and the
** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and
** will overwrite any prior [sqlite3_wal_hook()] settings.
**
** ^If a write-ahead log callback is set using this function then
** [sqlite3_wal_checkpoint_v2()] or [PRAGMA wal_checkpoint]
** should be invoked periodically to keep the write-ahead log file
** from growing without bound.
**
** ^Passing a NULL pointer for the callback disables automatic
** checkpointing entirely. To re-enable the default behavior, call
** sqlite3_wal_autocheckpoint(db,1000) or use [PRAGMA wal_checkpoint].
*/
SQLITE_API void *sqlite3_wal_hook(
sqlite3*,
@@ -9808,7 +9899,7 @@ SQLITE_API void *sqlite3_wal_hook(
** to automatically [checkpoint]
** after committing a transaction if there are N or
** more frames in the [write-ahead log] file. ^Passing zero or
** a negative value as the nFrame parameter disables automatic
** a negative value as the N parameter disables automatic
** checkpoints entirely.
**
** ^The callback registered by this function replaces any existing callback
@@ -9824,9 +9915,10 @@ SQLITE_API void *sqlite3_wal_hook(
**
** ^Every new [database connection] defaults to having the auto-checkpoint
** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT]
** pages. The use of this interface
** is only necessary if the default setting is found to be suboptimal
** for a particular application.
** pages.
**
** ^The use of this interface is only necessary if the default setting
** is found to be suboptimal for a particular application.
*/
SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N);
@@ -9891,6 +9983,11 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the
** addition that it also truncates the log file to zero bytes just prior
** to a successful return.
**
** <dt>SQLITE_CHECKPOINT_NOOP<dd>
** ^This mode always checkpoints zero frames. The only reason to invoke
** a NOOP checkpoint is to access the values returned by
** sqlite3_wal_checkpoint_v2() via output parameters *pnLog and *pnCkpt.
** </dl>
**
** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in
@@ -9961,6 +10058,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the
** meaning of each of these checkpoint modes.
*/
#define SQLITE_CHECKPOINT_NOOP -1 /* Do no work at all */
#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */
#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */
#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */
@@ -10329,7 +10427,7 @@ SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle);
** &nbsp; ){
** &nbsp; // do something with pVal
** &nbsp; }
** &nbsp; if( rc!=SQLITE_OK ){
** &nbsp; if( rc!=SQLITE_DONE ){
** &nbsp; // an error has occurred
** &nbsp; }
** </pre></blockquote>)^
@@ -10788,7 +10886,7 @@ typedef struct sqlite3_snapshot {
** The [sqlite3_snapshot_get()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
SQLITE_API int sqlite3_snapshot_get(
sqlite3 *db,
const char *zSchema,
sqlite3_snapshot **ppSnapshot
@@ -10837,7 +10935,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
** The [sqlite3_snapshot_open()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
SQLITE_API int sqlite3_snapshot_open(
sqlite3 *db,
const char *zSchema,
sqlite3_snapshot *pSnapshot
@@ -10854,7 +10952,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
** The [sqlite3_snapshot_free()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot*);
/*
** CAPI3REF: Compare the ages of two snapshot handles.
@@ -10881,7 +10979,7 @@ SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
** This interface is only available if SQLite is compiled with the
** [SQLITE_ENABLE_SNAPSHOT] option.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
SQLITE_API int sqlite3_snapshot_cmp(
sqlite3_snapshot *p1,
sqlite3_snapshot *p2
);
@@ -10909,7 +11007,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
** This interface is only available if SQLite is compiled with the
** [SQLITE_ENABLE_SNAPSHOT] option.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
/*
** CAPI3REF: Serialize a database
@@ -10983,12 +11081,13 @@ SQLITE_API unsigned char *sqlite3_serialize(
**
** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the
** [database connection] D to disconnect from database S and then
** reopen S as an in-memory database based on the serialization contained
** in P. The serialized database P is N bytes in size. M is the size of
** the buffer P, which might be larger than N. If M is larger than N, and
** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is
** permitted to add content to the in-memory database as long as the total
** size does not exceed M bytes.
** reopen S as an in-memory database based on the serialization
** contained in P. If S is a NULL pointer, the main database is
** used. The serialized database P is N bytes in size. M is the size
** of the buffer P, which might be larger than N. If M is larger than
** N, and the SQLITE_DESERIALIZE_READONLY bit is not set in F, then
** SQLite is permitted to add content to the in-memory database as
** long as the total size does not exceed M bytes.
**
** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will
** invoke sqlite3_free() on the serialization buffer when the database
@@ -11055,6 +11154,54 @@ SQLITE_API int sqlite3_deserialize(
#define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */
#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */
/*
** CAPI3REF: Bind array values to the CARRAY table-valued function
**
** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to
** one of the first argument of the [carray() table-valued function]. The
** S parameter is a pointer to the [prepared statement] that uses the carray()
** functions. I is the parameter index to be bound. P is a pointer to the
** array to be bound, and N is the number of eements in the array. The
** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64],
** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to
** indicate the datatype of the array being bound. The X argument is not a
** NULL pointer, then SQLite will invoke the function X on the P parameter
** after it has finished using P, even if the call to
** sqlite3_carray_bind() fails. The special-case finalizer
** SQLITE_TRANSIENT has no effect here.
*/
SQLITE_API int sqlite3_carray_bind(
sqlite3_stmt *pStmt, /* Statement to be bound */
int i, /* Parameter index */
void *aData, /* Pointer to array data */
int nData, /* Number of data elements */
int mFlags, /* CARRAY flags */
void (*xDel)(void*) /* Destructor for aData */
);
/*
** CAPI3REF: Datatypes for the CARRAY table-valued function
**
** The fifth argument to the [sqlite3_carray_bind()] interface musts be
** one of the following constants, to specify the datatype of the array
** that is being bound into the [carray table-valued function].
*/
#define SQLITE_CARRAY_INT32 0 /* Data is 32-bit signed integers */
#define SQLITE_CARRAY_INT64 1 /* Data is 64-bit signed integers */
#define SQLITE_CARRAY_DOUBLE 2 /* Data is doubles */
#define SQLITE_CARRAY_TEXT 3 /* Data is char* */
#define SQLITE_CARRAY_BLOB 4 /* Data is struct iovec */
/*
** Versions of the above #defines that omit the initial SQLITE_, for
** legacy compatibility.
*/
#define CARRAY_INT32 0 /* Data is 32-bit signed integers */
#define CARRAY_INT64 1 /* Data is 64-bit signed integers */
#define CARRAY_DOUBLE 2 /* Data is doubles */
#define CARRAY_TEXT 3 /* Data is char* */
#define CARRAY_BLOB 4 /* Data is struct iovec */
/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
@@ -12314,14 +12461,32 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*);
** update the "main" database attached to handle db with the changes found in
** the changeset passed via the second and third arguments.
**
** All changes made by these functions are enclosed in a savepoint transaction.
** If any other error (aside from a constraint failure when attempting to
** write to the target database) occurs, then the savepoint transaction is
** rolled back, restoring the target database to its original state, and an
** SQLite error code returned. Additionally, starting with version 3.51.0,
** an error code and error message that may be accessed using the
** [sqlite3_errcode()] and [sqlite3_errmsg()] APIs are left in the database
** handle.
**
** The fourth argument (xFilter) passed to these functions is the "filter
** callback". If it is not NULL, then for each table affected by at least one
** change in the changeset, the filter callback is invoked with
** the table name as the second argument, and a copy of the context pointer
** passed as the sixth argument as the first. If the "filter callback"
** returns zero, then no attempt is made to apply any changes to the table.
** Otherwise, if the return value is non-zero or the xFilter argument to
** is NULL, all changes related to the table are attempted.
** callback". This may be passed NULL, in which case all changes in the
** changeset are applied to the database. For sqlite3changeset_apply() and
** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once
** for each table affected by at least one change in the changeset. In this
** case the table name is passed as the second argument, and a copy of
** the context pointer passed as the sixth argument to apply() or apply_v2()
** as the first. If the "filter callback" returns zero, then no attempt is
** made to apply any changes to the table. Otherwise, if the return value is
** non-zero, all changes related to the table are attempted.
**
** For sqlite3_changeset_apply_v3(), the xFilter callback is invoked once
** per change. The second argument in this case is an sqlite3_changeset_iter
** that may be queried using the usual APIs for the details of the current
** change. If the "filter callback" returns zero in this case, then no attempt
** is made to apply the current change. If it returns non-zero, the change
** is applied.
**
** For each table that is not excluded by the filter callback, this function
** tests that the target database contains a compatible table. A table is
@@ -12342,11 +12507,11 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*);
** one such warning is issued for each table in the changeset.
**
** For each change for which there is a compatible table, an attempt is made
** to modify the table contents according to the UPDATE, INSERT or DELETE
** change. If a change cannot be applied cleanly, the conflict handler
** function passed as the fifth argument to sqlite3changeset_apply() may be
** invoked. A description of exactly when the conflict handler is invoked for
** each type of change is below.
** to modify the table contents according to each UPDATE, INSERT or DELETE
** change that is not excluded by a filter callback. If a change cannot be
** applied cleanly, the conflict handler function passed as the fifth argument
** to sqlite3changeset_apply() may be invoked. A description of exactly when
** the conflict handler is invoked for each type of change is below.
**
** Unlike the xFilter argument, xConflict may not be passed NULL. The results
** of passing anything other than a valid function pointer as the xConflict
@@ -12442,12 +12607,6 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*);
** This can be used to further customize the application's conflict
** resolution strategy.
**
** All changes made by these functions are enclosed in a savepoint transaction.
** If any other error (aside from a constraint failure when attempting to
** write to the target database) occurs, then the savepoint transaction is
** rolled back, restoring the target database to its original state, and an
** SQLite error code returned.
**
** If the output parameters (ppRebase) and (pnRebase) are non-NULL and
** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2()
** may set (*ppRebase) to point to a "rebase" that may be used with the
@@ -12497,6 +12656,23 @@ SQLITE_API int sqlite3changeset_apply_v2(
void **ppRebase, int *pnRebase, /* OUT: Rebase data */
int flags /* SESSION_CHANGESETAPPLY_* flags */
);
SQLITE_API int sqlite3changeset_apply_v3(
sqlite3 *db, /* Apply change to "main" db of this handle */
int nChangeset, /* Size of changeset in bytes */
void *pChangeset, /* Changeset blob */
int(*xFilter)(
void *pCtx, /* Copy of sixth arg to _apply() */
sqlite3_changeset_iter *p /* Handle describing change */
),
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
sqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx, /* First argument passed to xConflict */
void **ppRebase, int *pnRebase, /* OUT: Rebase data */
int flags /* SESSION_CHANGESETAPPLY_* flags */
);
/*
** CAPI3REF: Flags for sqlite3changeset_apply_v2
@@ -12916,6 +13092,23 @@ SQLITE_API int sqlite3changeset_apply_v2_strm(
void **ppRebase, int *pnRebase,
int flags
);
SQLITE_API int sqlite3changeset_apply_v3_strm(
sqlite3 *db, /* Apply change to "main" db of this handle */
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
void *pIn, /* First arg for xInput */
int(*xFilter)(
void *pCtx, /* Copy of sixth arg to _apply() */
sqlite3_changeset_iter *p
),
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
sqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx, /* First argument passed to xConflict */
void **ppRebase, int *pnRebase,
int flags
);
SQLITE_API int sqlite3changeset_concat_strm(
int (*xInputA)(void *pIn, void *pData, int *pnData),
void *pInA,
+1 -1
View File
@@ -6,7 +6,7 @@ package sqlite3
/*
#ifndef USE_LIBSQLITE3
#cgo CFLAGS: -DSQLITE_ENABLE_COLUMN_METADATA
#include <sqlite3-binding.h>
#include "sqlite3-binding.h"
#else
#include <sqlite3.h>
#endif
+1 -1
View File
@@ -5,7 +5,7 @@ package sqlite3
/*
#ifndef USE_LIBSQLITE3
#include <sqlite3-binding.h>
#include "sqlite3-binding.h"
#else
#include <sqlite3.h>
#endif
+7
View File
@@ -373,6 +373,10 @@ struct sqlite3_api_routines {
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
/* Version 3.50.0 and later */
int (*setlk_timeout)(sqlite3*,int,int);
/* Version 3.51.0 and later */
int (*set_errmsg)(sqlite3*,int,const char*);
int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int);
};
/*
@@ -708,6 +712,9 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_set_clientdata sqlite3_api->set_clientdata
/* Version 3.50.0 and later */
#define sqlite3_setlk_timeout sqlite3_api->setlk_timeout
/* Version 3.51.0 and later */
#define sqlite3_set_errmsg sqlite3_api->set_errmsg
#define sqlite3_db_status64 sqlite3_api->db_status64
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
+34 -18
View File
@@ -3022,7 +3022,7 @@ func (c *client) processSubEx(subject, queue, bsid []byte, cb msgHandler, noForw
return sub, nil
}
if err := c.addShadowSubscriptions(acc, sub, true); err != nil {
if err := c.addShadowSubscriptions(acc, sub); err != nil {
c.Errorf(err.Error())
}
@@ -3052,10 +3052,7 @@ type ime struct {
// If the client's account has stream imports and there are matches for this
// subscription's subject, then add shadow subscriptions in the other accounts
// that export this subject.
//
// enact=false allows MQTT clients to get the list of shadow subscriptions
// without enacting them, in order to first obtain matching "retained" messages.
func (c *client) addShadowSubscriptions(acc *Account, sub *subscription, enact bool) error {
func (c *client) addShadowSubscriptions(acc *Account, sub *subscription) error {
if acc == nil {
return ErrMissingAccount
}
@@ -3158,7 +3155,7 @@ func (c *client) addShadowSubscriptions(acc *Account, sub *subscription, enact b
for i := 0; i < len(ims); i++ {
ime := &ims[i]
// We will create a shadow subscription.
nsub, err := c.addShadowSub(sub, ime, enact)
nsub, err := c.addShadowSub(sub, ime)
if err != nil {
return err
}
@@ -3175,7 +3172,7 @@ func (c *client) addShadowSubscriptions(acc *Account, sub *subscription, enact b
}
// Add in the shadow subscription.
func (c *client) addShadowSub(sub *subscription, ime *ime, enact bool) (*subscription, error) {
func (c *client) addShadowSub(sub *subscription, ime *ime) (*subscription, error) {
c.mu.Lock()
nsub := *sub // copy
c.mu.Unlock()
@@ -3203,10 +3200,6 @@ func (c *client) addShadowSub(sub *subscription, ime *ime, enact bool) (*subscri
}
// Else use original subject
if !enact {
return &nsub, nil
}
c.Debugf("Creating import subscription on %q from account %q", nsub.subject, im.acc.Name)
if err := im.acc.sl.Insert(&nsub); err != nil {
@@ -3237,7 +3230,7 @@ func (c *client) canSubscribe(subject string, optQueue ...string) bool {
return true
}
allowed := true
allowed, checkAllow := true, true
// Optional queue group.
var queue string
@@ -3245,8 +3238,14 @@ func (c *client) canSubscribe(subject string, optQueue ...string) bool {
queue = optQueue[0]
}
// For CLIENT connections that are MQTT, or other types of connections, we will
// implicitly allow anything that starts with the "$MQTT." prefix. However,
// we don't just return here, we skip the check for "allow" but will check "deny".
if (c.isMqtt() || (c.kind != CLIENT)) && strings.HasPrefix(subject, mqttPrefix) {
checkAllow = false
}
// Check allow list. If no allow list that means all are allowed. Deny can overrule.
if c.perms.sub.allow != nil {
if checkAllow && c.perms.sub.allow != nil {
r := c.perms.sub.allow.Match(subject)
allowed = len(r.psubs) > 0
if queue != _EMPTY_ && len(r.qsubs) > 0 {
@@ -4063,9 +4062,15 @@ func (c *client) pubAllowedFullCheck(subject string, fullCheck, hasLock bool) bo
if ok {
return v.(bool)
}
allowed := true
allowed, checkAllow := true, true
// For CLIENT connections that are MQTT, or other types of connections, we will
// implicitly allow anything that starts with the "$MQTT." prefix. However,
// we don't just return here, we skip the check for "allow" but will check "deny".
if (c.isMqtt() || c.kind != CLIENT) && strings.HasPrefix(subject, mqttPrefix) {
checkAllow = false
}
// Cache miss, check allow then deny as needed.
if c.perms.pub.allow != nil {
if checkAllow && c.perms.pub.allow != nil {
np, _ := c.perms.pub.allow.NumInterest(subject)
allowed = np != 0
}
@@ -5341,8 +5346,10 @@ sendToRoutesOrLeafs:
// If we do have a deliver subject we need to do something with it.
// Again this is when JetStream (but possibly others) wants the system
// to rewrite the delivered subject. The way we will do that is place it
// at the end of the reply subject if it exists.
if len(deliver) > 0 && len(reply) > 0 {
// at the end of the reply subject if it exists. But only if this wasn't
// already performed, otherwise we'd end up with a duplicate '@' suffix
// resulting in a protocol error.
if len(deliver) > 0 && len(reply) > 0 && !remapped {
reply = append(reply, '@')
reply = append(reply, deliver...)
}
@@ -5501,6 +5508,9 @@ func (c *client) processPingTimer() {
if c.kind == ROUTER && opts.Cluster.PingInterval > 0 {
pingInterval = opts.Cluster.PingInterval
}
if c.isWebsocket() && opts.Websocket.PingInterval > 0 {
pingInterval = opts.Websocket.PingInterval
}
pingInterval = adjustPingInterval(c.kind, pingInterval)
now := time.Now()
needRTT := c.rtt == 0 || now.Sub(c.rttStart) > DEFAULT_RTT_MEASUREMENT_INTERVAL
@@ -5583,6 +5593,9 @@ func (c *client) setPingTimer() {
if c.kind == ROUTER && opts.Cluster.PingInterval > 0 {
d = opts.Cluster.PingInterval
}
if c.isWebsocket() && opts.Websocket.PingInterval > 0 {
d = opts.Websocket.PingInterval
}
d = adjustPingInterval(c.kind, d)
c.ping.tmr = time.AfterFunc(d, c.processPingTimer)
}
@@ -5788,7 +5801,7 @@ func (c *client) processSubsOnConfigReload(awcsti map[string]struct{}) {
oldShadows := sub.shadow
sub.shadow = nil
c.mu.Unlock()
c.addShadowSubscriptions(acc, sub, true)
c.addShadowSubscriptions(acc, sub)
for _, nsub := range oldShadows {
nsub.im.acc.sl.Remove(nsub)
}
@@ -6617,6 +6630,9 @@ func (c *client) setFirstPingTimer() {
if c.kind == ROUTER && opts.Cluster.PingInterval > 0 {
d = opts.Cluster.PingInterval
}
if c.isWebsocket() && opts.Websocket.PingInterval > 0 {
d = opts.Websocket.PingInterval
}
if !opts.DisableShortFirstPing {
if c.kind != CLIENT {
if d > firstPingInterval {
+1 -1
View File
@@ -66,7 +66,7 @@ func init() {
const (
// VERSION is the current version for the server.
VERSION = "2.12.2"
VERSION = "2.12.3"
// PROTO is the currently supported protocol.
// 0 was the original
+51 -35
View File
@@ -963,7 +963,7 @@ func (mset *stream) addConsumerWithAssignment(config *ConsumerConfig, oname stri
}
mset.mu.RLock()
s, jsa, cfg, acc := mset.srv, mset.jsa, mset.cfg, mset.acc
s, js, jsa, cfg, acc := mset.srv, mset.js, mset.jsa, mset.cfg, mset.acc
mset.mu.RUnlock()
// If we do not have the consumer currently assigned to us in cluster mode we will proceed but warn.
@@ -1134,6 +1134,13 @@ func (mset *stream) addConsumerWithAssignment(config *ConsumerConfig, oname stri
created: time.Now().UTC(),
}
// Add created timestamp used for the store, must match that of the consumer assignment if it exists.
if ca != nil {
js.mu.RLock()
o.created = ca.Created
js.mu.RUnlock()
}
// Bind internal client to the user account.
o.client.registerWithAccount(a)
// Bind to the system account.
@@ -1186,7 +1193,7 @@ func (mset *stream) addConsumerWithAssignment(config *ConsumerConfig, oname stri
// Setup our storage if not a direct consumer.
if !config.Direct {
store, err := mset.store.ConsumerStore(o.name, config)
store, err := mset.store.ConsumerStore(o.name, o.created, config)
if err != nil {
mset.mu.Unlock()
o.deleteWithoutAdvisory()
@@ -3133,6 +3140,12 @@ func (o *consumer) infoWithSnapAndReply(snap bool, reply string) *ConsumerInfo {
})
}
np, err := o.checkNumPending()
if err != nil {
o.mu.Unlock()
return nil
}
cfg := o.cfg
info := &ConsumerInfo{
Stream: o.stream,
@@ -3149,7 +3162,7 @@ func (o *consumer) infoWithSnapAndReply(snap bool, reply string) *ConsumerInfo {
},
NumAckPending: len(o.pending),
NumRedelivered: len(o.rdc),
NumPending: o.checkNumPending(),
NumPending: np,
PushBound: o.isPushMode() && o.active,
TimeStamp: time.Now().UTC(),
PriorityGroups: priorityGroups,
@@ -5097,17 +5110,17 @@ func (o *consumer) setMaxPendingBytes(limit int) {
// The race is a getNextMsg skips a deleted msg, and then the decStreamPending call fires.
// This does some quick sanity checks to see if we should re-calculate num pending.
// Lock should be held.
func (o *consumer) checkNumPending() uint64 {
func (o *consumer) checkNumPending() (uint64, error) {
if o.mset != nil && o.mset.store != nil {
var state StreamState
o.mset.store.FastState(&state)
npc := o.numPending()
if o.sseq > state.LastSeq && npc > 0 || npc > state.Msgs {
// Re-calculate.
o.streamNumPending()
return o.streamNumPending()
}
}
return o.numPending()
return o.numPending(), nil
}
// Lock should be held.
@@ -5134,7 +5147,7 @@ func (o *consumer) checkNumPendingOnEOF() {
}
// Call into streamNumPending after acquiring the consumer lock.
func (o *consumer) streamNumPendingLocked() uint64 {
func (o *consumer) streamNumPendingLocked() (uint64, error) {
o.mu.Lock()
defer o.mu.Unlock()
return o.streamNumPending()
@@ -5143,22 +5156,25 @@ func (o *consumer) streamNumPendingLocked() uint64 {
// Will force a set from the stream store of num pending.
// Depends on delivery policy, for last per subject we calculate differently.
// Lock should be held.
func (o *consumer) streamNumPending() uint64 {
func (o *consumer) streamNumPending() (uint64, error) {
if o.mset == nil || o.mset.store == nil {
o.npc, o.npf = 0, 0
return 0
return 0, nil
}
npc, npf, err := o.calculateNumPending()
if err != nil {
return 0, err
}
npc, npf := o.calculateNumPending()
o.npc, o.npf = int64(npc), npf
return o.numPending()
return o.numPending(), nil
}
// Will calculate num pending but only requires a read lock.
// Depends on delivery policy, for last per subject we calculate differently.
// At least RLock should be held.
func (o *consumer) calculateNumPending() (npc, npf uint64) {
func (o *consumer) calculateNumPending() (npc, npf uint64, err error) {
if o.mset == nil || o.mset.store == nil {
return 0, 0
return 0, 0, nil
}
isLastPerSubject := o.cfg.DeliverPolicy == DeliverLastPerSubject
@@ -6445,6 +6461,10 @@ func (o *consumer) checkStateForInterestStream(ss *StreamState) error {
if asflr&(1<<63) != 0 {
return errAckFloorInvalid
}
dflr := asflr
if len(state.Pending) > 0 && state.Delivered.Stream > dflr {
dflr = state.Delivered.Stream
}
// Check if the underlying stream's last sequence is less than our floor.
// This can happen if the stream has been reset and has not caught up yet.
@@ -6463,7 +6483,7 @@ func (o *consumer) checkStateForInterestStream(ss *StreamState) error {
}
var retryAsflr uint64
for seq = fseq; asflr > 0 && seq <= asflr; seq++ {
for seq = fseq; dflr > 0 && seq <= dflr; seq++ {
if filters != nil {
_, nseq, err = store.LoadNextMsgMulti(filters, seq, &smv)
} else {
@@ -6473,14 +6493,24 @@ func (o *consumer) checkStateForInterestStream(ss *StreamState) error {
if nseq > seq {
seq = nseq
}
// Only ack though if no error and seq <= ack floor.
if err == nil && seq <= asflr {
didRemove := mset.ackMsg(o, seq)
// Removing the message could fail. For example if clustered since we need to propose it.
// Overwrite retry floor (only the first time) to allow us to check next time if the removal was successful.
if didRemove && retryAsflr == 0 {
retryAsflr = seq
if err == nil {
// Only ack though if no error and seq <= ack floor.
if seq <= asflr {
didRemove := mset.ackMsg(o, seq)
// Removing the message could fail. For example if clustered since we need to propose it.
// Overwrite retry floor (only the first time) to allow us to check next time if the removal was successful.
if didRemove && retryAsflr == 0 {
retryAsflr = seq
}
} else if seq <= dflr {
// If we have pending, we will need to walk through to delivered in case we missed any of those acks as well.
if _, ok := state.Pending[seq]; !ok {
// The filters are already taken into account,
mset.ackMsg(o, seq)
}
}
} else if err == ErrStoreEOF {
break
}
}
// If retry floor was not overwritten, set to ack floor+1, we don't need to account for any retries below it.
@@ -6494,21 +6524,7 @@ func (o *consumer) checkStateForInterestStream(ss *StreamState) error {
if retryAsflr > o.chkflr {
o.chkflr = retryAsflr
}
// See if we need to process this update if our parent stream is not a limits policy stream.
state, _ = o.store.State()
o.mu.Unlock()
// If we have pending, we will need to walk through to delivered in case we missed any of those acks as well.
if state != nil && len(state.Pending) > 0 && state.AckFloor.Stream > 0 {
for seq := state.AckFloor.Stream + 1; seq <= state.Delivered.Stream; seq++ {
if _, ok := state.Pending[seq]; !ok {
// Want to call needAck since it is filter aware.
if o.needAck(seq, _EMPTY_) {
mset.ackMsg(o, seq)
}
}
}
}
return nil
}
+10
View File
@@ -1998,5 +1998,15 @@
"help": "",
"url": "",
"deprecates": ""
},
{
"constant": "JSClusterServerMemberChangeInflightErr",
"code": 400,
"error_code": 10202,
"description": "cluster member change is in progress",
"comment": "",
"help": "",
"url": "",
"deprecates": ""
}
]
File diff suppressed because it is too large Load Diff
+5
View File
@@ -51,6 +51,11 @@ var (
// unnecessary allocations.
type SimpleSublist = GenericSublist[struct{}]
// NewSimpleSublist will create a simple sublist.
func NewSimpleSublist() *SimpleSublist {
return &GenericSublist[struct{}]{root: newLevel[struct{}]()}
}
// A GenericSublist stores and efficiently retrieves subscriptions.
type GenericSublist[T comparable] struct {
sync.RWMutex
+26 -10
View File
@@ -2689,8 +2689,17 @@ func (s *Server) jsLeaderServerRemoveRequest(sub *subscription, c *client, _ *Ac
return
}
js.mu.Lock()
defer js.mu.Unlock()
// Another peer-remove is already in progress, don't allow multiple concurrent changes.
if cc.peerRemoveReply != nil {
resp.Error = NewJSClusterServerMemberChangeInflightError()
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
var found string
js.mu.RLock()
for _, p := range meta.Peers() {
// If Peer is specified, it takes precedence
if req.Peer != _EMPTY_ {
@@ -2706,7 +2715,6 @@ func (s *Server) jsLeaderServerRemoveRequest(sub *subscription, c *client, _ *Ac
break
}
}
js.mu.RUnlock()
if found == _EMPTY_ {
resp.Error = NewJSClusterServerNotMemberError()
@@ -2714,13 +2722,21 @@ func (s *Server) jsLeaderServerRemoveRequest(sub *subscription, c *client, _ *Ac
return
}
// So we have a valid peer.
js.mu.Lock()
meta.ProposeRemovePeer(found)
js.mu.Unlock()
if err := meta.ProposeRemovePeer(found); err != nil {
if err == errMembershipChange {
resp.Error = NewJSClusterServerMemberChangeInflightError()
} else {
resp.Error = NewJSRaftGeneralError(err)
}
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
return
}
resp.Success = true
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
if cc.peerRemoveReply == nil {
cc.peerRemoveReply = make(map[string]peerRemoveInfo, 1)
}
// Only copy the request, the subject and reply are already copied.
cc.peerRemoveReply[found] = peerRemoveInfo{ci: ci, subject: subject, reply: reply, request: string(msg)}
}
func (s *Server) peerSetToNames(ps []string) []string {
@@ -3114,11 +3130,11 @@ func (s *Server) jsLeaderAccountPurgeRequest(sub *subscription, c *client, _ *Ac
for _, osa := range streams {
for _, oca := range osa.consumers {
oca.deleted = true
ca := &consumerAssignment{Group: oca.Group, Stream: oca.Stream, Name: oca.Name, Config: oca.Config, Subject: subject, Client: oca.Client}
ca := &consumerAssignment{Group: oca.Group, Stream: oca.Stream, Name: oca.Name, Config: oca.Config, Subject: subject, Client: oca.Client, Created: oca.Created}
meta.Propose(encodeDeleteConsumerAssignment(ca))
nc++
}
sa := &streamAssignment{Group: osa.Group, Config: osa.Config, Subject: subject, Client: osa.Client}
sa := &streamAssignment{Group: osa.Group, Config: osa.Config, Subject: subject, Client: osa.Client, Created: osa.Created}
meta.Propose(encodeDeleteStreamAssignment(sa))
ns++
}
+35 -30
View File
@@ -611,41 +611,46 @@ func checkMsgHeadersPreClusteredProposal(
// We need to deny here otherwise we'd need to bump CLFS, and it could succeed on some
// peers and not others depending on consumer ack state (if interest policy).
// So we deny here, if we allow that means we know it would succeed on every peer.
if discard == DiscardNew && (maxMsgs > 0 || maxBytes > 0) {
// Error if over DiscardNew per subject threshold.
if discardNewPer {
totalMsgsForSubject := i.ops
if i, ok = mset.inflight[subject]; ok {
totalMsgsForSubject += i.ops
if discard == DiscardNew {
if maxMsgs > 0 || maxBytes > 0 {
// Track usual max msgs/bytes thresholds for DiscardNew.
var state StreamState
mset.store.FastState(&state)
totalMsgs := state.Msgs
totalBytes := state.Bytes
for _, i = range mset.inflight {
totalMsgs += i.ops
totalBytes += i.bytes
}
if maxMsgsPer > 0 && totalMsgsForSubject > uint64(maxMsgsPer) {
err = ErrMaxMsgsPerSubject
for _, i = range diff.inflight {
totalMsgs += i.ops
totalBytes += i.bytes
}
if maxMsgs > 0 && totalMsgs > uint64(maxMsgs) {
err = ErrMaxMsgs
} else if maxBytes > 0 && totalBytes > uint64(maxBytes) {
err = ErrMaxBytes
}
if err != nil {
return hdr, msg, 0, NewJSStreamStoreFailedError(err, Unless(err)), err
}
}
// Track usual max msgs/bytes thresholds for DiscardNew.
var state StreamState
mset.store.FastState(&state)
totalMsgs := state.Msgs
totalBytes := state.Bytes
for _, i = range mset.inflight {
totalMsgs += i.ops
totalBytes += i.bytes
}
for _, i = range diff.inflight {
totalMsgs += i.ops
totalBytes += i.bytes
}
if maxMsgs > 0 && totalMsgs > uint64(maxMsgs) {
err = ErrMaxMsgs
} else if maxBytes > 0 && totalBytes > uint64(maxBytes) {
err = ErrMaxBytes
}
if err != nil {
return hdr, msg, 0, NewJSStreamStoreFailedError(err, Unless(err)), err
// Similarly, check DiscardNew per-subject threshold to not need to bump CLFS.
if discardNewPer && maxMsgsPer > 0 {
// Get the current total for this subject.
totalMsgsForSubject := mset.store.SubjectsTotals(subject)[subject]
// Add inflight count in this batch and for this stream.
totalMsgsForSubject += i.ops
if i, ok = mset.inflight[subject]; ok {
totalMsgsForSubject += i.ops
}
if totalMsgsForSubject > uint64(maxMsgsPer) {
err = ErrMaxMsgsPerSubject
return hdr, msg, 0, NewJSStreamStoreFailedError(err, Unless(err)), err
}
}
}
+186 -99
View File
@@ -51,6 +51,9 @@ type jetStreamCluster struct {
// concurrent requests for same account and stream we need to let it process to get
// a response but they need to be same group, peers etc. and sync subjects.
inflight map[string]map[string]*inflightInfo
// Holds a map of a peer ID to the reply subject, to only respond after gaining
// quorum on the peer-remove action.
peerRemoveReply map[string]peerRemoveInfo
// Signals meta-leader should check the stream assignments.
streamsCheck bool
// Server.
@@ -84,6 +87,14 @@ type inflightInfo struct {
cfg *StreamConfig
}
// Used to track inflight peer-remove info to respond 'success' after quorum.
type peerRemoveInfo struct {
ci *ClientInfo
subject string
reply string
request string
}
// Used to guide placement of streams and meta controllers in clustered JetStream.
type Placement struct {
Cluster string `json:"cluster,omitempty"`
@@ -625,8 +636,11 @@ func (js *jetStream) isStreamHealthy(acc *Account, sa *streamAssignment) error {
}
msetNode := mset.raftNode()
mset.cfgMu.RLock()
replicas := mset.cfg.Replicas
mset.cfgMu.RUnlock()
switch {
case mset.cfg.Replicas <= 1:
case replicas <= 1:
return nil // No further checks for R=1 streams
case node == nil:
@@ -1248,14 +1262,11 @@ func (ru *recoveryUpdates) removeStream(sa *streamAssignment) {
func (ru *recoveryUpdates) addStream(sa *streamAssignment) {
key := sa.recoveryKey()
ru.addStreams[key] = sa
delete(ru.removeStreams, key)
}
func (ru *recoveryUpdates) updateStream(sa *streamAssignment) {
key := sa.recoveryKey()
ru.updateStreams[key] = sa
delete(ru.addStreams, key)
delete(ru.removeStreams, key)
}
func (ru *recoveryUpdates) removeConsumer(ca *consumerAssignment) {
@@ -1273,9 +1284,6 @@ func (ru *recoveryUpdates) removeConsumer(ca *consumerAssignment) {
func (ru *recoveryUpdates) addOrUpdateConsumer(ca *consumerAssignment) {
key := ca.recoveryKey()
skey := ca.streamRecoveryKey()
if consumers, ok := ru.removeConsumers[skey]; ok {
delete(consumers, key)
}
if _, ok := ru.updateConsumers[skey]; !ok {
ru.updateConsumers[skey] = map[string]*consumerAssignment{}
}
@@ -1405,11 +1413,12 @@ func (js *jetStream) monitorCluster() {
// Set to true to start.
js.setMetaRecovering()
recovering := true
// Snapshotting function.
doSnapshot := func(force bool) {
// Suppress during recovery.
if js.isMetaRecovering() {
if recovering {
return
}
// Look up what the threshold is for compaction. Re-reading from config here as it is reloadable.
@@ -1433,13 +1442,7 @@ func (js *jetStream) monitorCluster() {
}
}
ru := &recoveryUpdates{
removeStreams: make(map[string]*streamAssignment),
removeConsumers: make(map[string]map[string]*consumerAssignment),
addStreams: make(map[string]*streamAssignment),
updateStreams: make(map[string]*streamAssignment),
updateConsumers: make(map[string]map[string]*consumerAssignment),
}
var ru *recoveryUpdates
// Make sure to cancel any pending checkForOrphans calls if the
// monitor goroutine exits.
@@ -1453,8 +1456,7 @@ func (js *jetStream) monitorCluster() {
doSnapshot(false)
return
case <-rqch:
// Clean signal from shutdown routine so do best effort attempt to snapshot meta layer.
doSnapshot(false)
// Raft node is closed, no use in trying to snapshot.
return
case <-qch:
// Clean signal from shutdown routine so do best effort attempt to snapshot meta layer.
@@ -1463,43 +1465,58 @@ func (js *jetStream) monitorCluster() {
case <-aq.ch:
ces := aq.pop()
for _, ce := range ces {
if recovering && ru == nil {
ru = &recoveryUpdates{
removeStreams: make(map[string]*streamAssignment),
removeConsumers: make(map[string]map[string]*consumerAssignment),
addStreams: make(map[string]*streamAssignment),
updateStreams: make(map[string]*streamAssignment),
updateConsumers: make(map[string]map[string]*consumerAssignment),
}
}
if ce == nil {
// Process any removes that are still valid after recovery.
for _, cas := range ru.removeConsumers {
for _, ca := range cas {
js.processConsumerRemoval(ca)
if ru != nil {
// Process any removes that are still valid after recovery.
for _, cas := range ru.removeConsumers {
for _, ca := range cas {
js.processConsumerRemoval(ca)
}
}
}
for _, sa := range ru.removeStreams {
js.processStreamRemoval(sa)
}
// Process stream additions.
for _, sa := range ru.addStreams {
js.processStreamAssignment(sa)
}
// Process pending updates.
for _, sa := range ru.updateStreams {
js.processUpdateStreamAssignment(sa)
}
// Now consumers.
for _, cas := range ru.updateConsumers {
for _, ca := range cas {
js.processConsumerAssignment(ca)
for _, sa := range ru.removeStreams {
js.processStreamRemoval(sa)
}
// Process stream additions.
for _, sa := range ru.addStreams {
js.processStreamAssignment(sa)
}
// Process pending updates.
for _, sa := range ru.updateStreams {
js.processUpdateStreamAssignment(sa)
}
// Now consumers.
for _, cas := range ru.updateConsumers {
for _, ca := range cas {
js.processConsumerAssignment(ca)
}
}
}
// Signals we have replayed all of our metadata.
wasMetaRecovering := js.isMetaRecovering()
js.clearMetaRecovering()
recovering = false
// Clear.
ru = nil
s.Debugf("Recovered JetStream cluster metadata")
// Snapshot now so we start with freshly compacted log.
doSnapshot(true)
oc = time.AfterFunc(30*time.Second, js.checkForOrphans)
// Do a health check here as well.
go checkHealth()
if wasMetaRecovering {
oc = time.AfterFunc(30*time.Second, js.checkForOrphans)
// Do a health check here as well.
go checkHealth()
}
continue
}
if didSnap, err := js.applyMetaEntries(ce.Entries, ru); err == nil {
if isRecovering, didSnap, err := js.applyMetaEntries(ce.Entries, ru); err == nil {
var nb uint64
// Some entries can fail without an error when shutting down, don't move applied forward.
if !js.isShuttingDown() {
@@ -1510,6 +1527,7 @@ func (js *jetStream) monitorCluster() {
} else if nb > compactSizeMin && time.Since(lastSnapTime) > minSnapDelta {
doSnapshot(false)
}
recovering = isRecovering
} else {
s.Warnf("Error applying JetStream cluster entries: %v", err)
}
@@ -1702,7 +1720,7 @@ func (js *jetStream) metaSnapshot() ([]byte, error) {
return snap, nil
}
func (js *jetStream) applyMetaSnapshot(buf []byte, ru *recoveryUpdates, isRecovering bool) error {
func (js *jetStream) applyMetaSnapshot(buf []byte, ru *recoveryUpdates, isRecovering, startupRecovery bool) error {
var wsas []writeableStreamAssignment
if len(buf) > 0 {
jse, err := s2.Decode(nil, buf)
@@ -2080,20 +2098,59 @@ func (ca *consumerAssignment) recoveryKey() string {
return ca.Client.serviceAccount() + ksep + ca.Stream + ksep + ca.Name
}
func (js *jetStream) applyMetaEntries(entries []*Entry, ru *recoveryUpdates) (bool, error) {
func (js *jetStream) applyMetaEntries(entries []*Entry, ru *recoveryUpdates) (bool, bool, error) {
var didSnap bool
isRecovering := js.isMetaRecovering()
isRecovering := ru != nil
startupRecovery := js.isMetaRecovering()
for _, e := range entries {
// If we received a lower-level catchup entry, mark that we're recovering.
// We can optimize by staging all meta operations until we're caught up.
// At that point we can apply the diff in one go.
if e.Type == EntryCatchup {
isRecovering = true
// A catchup entry only contains this, so we can exit now and have the
// recoveryUpdates struct be populated for the next invocation of applyMetaEntries.
return isRecovering, didSnap, nil
}
if e.Type == EntrySnapshot {
js.applyMetaSnapshot(e.Data, ru, isRecovering)
js.applyMetaSnapshot(e.Data, ru, isRecovering, startupRecovery)
didSnap = true
} else if e.Type == EntryRemovePeer {
if !isRecovering {
js.processRemovePeer(string(e.Data))
if !js.isMetaRecovering() {
peer := string(e.Data)
js.processRemovePeer(peer)
// The meta leader can now respond to the peer-removal,
// since a quorum of nodes has this in their log.
s := js.srv
if s.JetStreamIsLeader() {
var (
info peerRemoveInfo
ok bool
)
js.mu.Lock()
if cc := js.cluster; cc != nil && cc.peerRemoveReply != nil {
if info, ok = cc.peerRemoveReply[peer]; ok {
delete(cc.peerRemoveReply, peer)
}
if len(cc.peerRemoveReply) == 0 {
cc.peerRemoveReply = nil
}
}
js.mu.Unlock()
if info.reply != _EMPTY_ {
sysAcc := s.SystemAccount()
var resp = JSApiMetaServerRemoveResponse{ApiResponse: ApiResponse{Type: JSApiMetaServerRemoveResponseType}}
resp.Success = true
s.sendAPIResponse(info.ci, sysAcc, info.subject, info.reply, info.request, s.jsonResponse(&resp))
}
}
}
} else if e.Type == EntryAddPeer {
if !isRecovering {
if !js.isMetaRecovering() {
js.processAddPeer(string(e.Data))
}
} else {
@@ -2103,7 +2160,7 @@ func (js *jetStream) applyMetaEntries(entries []*Entry, ru *recoveryUpdates) (bo
sa, err := decodeStreamAssignment(js.srv, buf[1:])
if err != nil {
js.srv.Errorf("JetStream cluster failed to decode stream assignment: %q", buf[1:])
return didSnap, err
return isRecovering, didSnap, err
}
if isRecovering {
js.setStreamAssignmentRecovering(sa)
@@ -2115,7 +2172,7 @@ func (js *jetStream) applyMetaEntries(entries []*Entry, ru *recoveryUpdates) (bo
sa, err := decodeStreamAssignment(js.srv, buf[1:])
if err != nil {
js.srv.Errorf("JetStream cluster failed to decode stream assignment: %q", buf[1:])
return didSnap, err
return isRecovering, didSnap, err
}
if isRecovering {
js.setStreamAssignmentRecovering(sa)
@@ -2127,7 +2184,7 @@ func (js *jetStream) applyMetaEntries(entries []*Entry, ru *recoveryUpdates) (bo
ca, err := decodeConsumerAssignment(buf[1:])
if err != nil {
js.srv.Errorf("JetStream cluster failed to decode consumer assignment: %q", buf[1:])
return didSnap, err
return isRecovering, didSnap, err
}
if isRecovering {
js.setConsumerAssignmentRecovering(ca)
@@ -2139,19 +2196,11 @@ func (js *jetStream) applyMetaEntries(entries []*Entry, ru *recoveryUpdates) (bo
ca, err := decodeConsumerAssignmentCompressed(buf[1:])
if err != nil {
js.srv.Errorf("JetStream cluster failed to decode compressed consumer assignment: %q", buf[1:])
return didSnap, err
return isRecovering, didSnap, err
}
if isRecovering {
js.setConsumerAssignmentRecovering(ca)
key := ca.recoveryKey()
skey := ca.streamRecoveryKey()
if consumers, ok := ru.removeConsumers[skey]; ok {
delete(consumers, key)
}
if _, ok := ru.updateConsumers[skey]; !ok {
ru.updateConsumers[skey] = map[string]*consumerAssignment{}
}
ru.updateConsumers[skey][key] = ca
ru.addOrUpdateConsumer(ca)
} else {
js.processConsumerAssignment(ca)
}
@@ -2159,7 +2208,7 @@ func (js *jetStream) applyMetaEntries(entries []*Entry, ru *recoveryUpdates) (bo
ca, err := decodeConsumerAssignment(buf[1:])
if err != nil {
js.srv.Errorf("JetStream cluster failed to decode consumer assignment: %q", buf[1:])
return didSnap, err
return isRecovering, didSnap, err
}
if isRecovering {
js.setConsumerAssignmentRecovering(ca)
@@ -2171,7 +2220,7 @@ func (js *jetStream) applyMetaEntries(entries []*Entry, ru *recoveryUpdates) (bo
sa, err := decodeStreamAssignment(js.srv, buf[1:])
if err != nil {
js.srv.Errorf("JetStream cluster failed to decode stream assignment: %q", buf[1:])
return didSnap, err
return isRecovering, didSnap, err
}
if isRecovering {
js.setStreamAssignmentRecovering(sa)
@@ -2184,7 +2233,7 @@ func (js *jetStream) applyMetaEntries(entries []*Entry, ru *recoveryUpdates) (bo
}
}
}
return didSnap, nil
return isRecovering, didSnap, nil
}
func (rg *raftGroup) isMember(id string) bool {
@@ -2665,12 +2714,7 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
}
return
case <-qch:
// Clean signal from shutdown routine so do best effort attempt to snapshot.
// Don't snapshot if not shutting down, Raft node could be going away on a
// scale down or remove for example.
if s.isShuttingDown() {
doSnapshot()
}
// Raft node is closed, no use in trying to snapshot.
return
case <-aq.ch:
var ne, nb uint64
@@ -2681,6 +2725,9 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
for _, ce := range ces {
// No special processing needed for when we are caught up on restart.
if ce == nil {
if !isRecovering {
continue
}
isRecovering = false
// If we are interest based make sure to check consumers if interest retention policy.
// This is to make sure we process any outstanding acks from all consumers.
@@ -2895,7 +2942,7 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
ci := js.clusterInfo(rg)
mset.checkClusterInfo(ci)
newPeers, oldPeers, newPeerSet, oldPeerSet := genPeerInfo(rg.Peers, len(rg.Peers)-replicas)
newPeers, _, newPeerSet, oldPeerSet := genPeerInfo(rg.Peers, len(rg.Peers)-replicas)
// If we are part of the new peerset and we have been passed the baton.
// We will handle scale down.
@@ -2923,11 +2970,8 @@ func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnaps
if needToWait {
continue
}
// We are good to go, can scale down here.
for _, p := range oldPeers {
n.ProposeRemovePeer(p)
}
n.ProposeKnownPeers(newPeers)
csa := sa.copyGroup()
csa.Group.Peers = newPeers
@@ -3209,6 +3253,12 @@ func (js *jetStream) applyStreamEntries(mset *stream, ce *CommittedEntry, isReco
mset.mu.RUnlock()
for i, e := range ce.Entries {
// Ignore if lower-level catchup is started.
// We don't need to optimize during this, all entries are handled as normal.
if e.Type == EntryCatchup {
continue
}
// Check if a batch is abandoned.
if e.Type != EntryNormal && batch != nil && batch.id != _EMPTY_ {
batch.rejectBatchState(mset)
@@ -4414,7 +4464,7 @@ func (js *jetStream) processClusterCreateStream(acc *Account, sa *streamAssignme
}
js.mu.RLock()
s, rg := js.srv, sa.Group
s, rg, created := js.srv, sa.Group, sa.Created
alreadyRunning := rg.node != nil
storage := sa.Config.Storage
restore := sa.Restore
@@ -4513,7 +4563,7 @@ func (js *jetStream) processClusterCreateStream(acc *Account, sa *streamAssignme
mset, err = acc.addStreamWithAssignment(sa.Config, nil, sa, false)
}
if mset != nil {
mset.setCreatedTime(sa.Created)
mset.setCreatedTime(created)
}
}
@@ -4600,7 +4650,7 @@ func (js *jetStream) processClusterCreateStream(acc *Account, sa *streamAssignme
mset, err = acc.lookupStream(sa.Config.Name)
if mset != nil {
mset.setStreamAssignment(sa)
mset.setCreatedTime(sa.Created)
mset.setCreatedTime(created)
}
}
if err != nil {
@@ -4688,12 +4738,15 @@ func (js *jetStream) processStreamRemoval(sa *streamAssignment) {
js.mu.Unlock()
return
}
stream := sa.Config.Name
isMember := sa.Group.isMember(cc.meta.ID())
wasLeader := cc.isStreamLeader(sa.Client.serviceAccount(), stream)
accName, stream, created := sa.Client.serviceAccount(), sa.Config.Name, sa.Created
var isMember bool
if sa.Group != nil {
isMember = sa.Group.isMember(cc.meta.ID())
}
wasLeader := cc.isStreamLeader(accName, stream)
// Check if we already have this assigned.
accStreams := cc.streams[sa.Client.serviceAccount()]
accStreams := cc.streams[accName]
needDelete := accStreams != nil && accStreams[stream] != nil
if needDelete {
if osa := accStreams[stream]; osa != nil && osa.unsupported != nil {
@@ -4705,11 +4758,22 @@ func (js *jetStream) processStreamRemoval(sa *streamAssignment) {
}
delete(accStreams, stream)
if len(accStreams) == 0 {
delete(cc.streams, sa.Client.serviceAccount())
delete(cc.streams, accName)
}
}
js.mu.Unlock()
// During initial/startup recovery we'll not have registered the stream assignment,
// but might have recovered the stream from disk. We'll need to make sure that we only
// delete the stream if it wasn't created after this delete.
if !needDelete && !created.IsZero() {
if acc, err := s.lookupOrFetchAccount(accName, isMember); err == nil {
if mset, err := acc.lookupStream(stream); err == nil {
needDelete = !mset.createdTime().After(created)
}
}
}
if needDelete {
js.processClusterDeleteStream(sa, isMember, wasLeader)
}
@@ -5001,11 +5065,12 @@ func (js *jetStream) processConsumerRemoval(ca *consumerAssignment) {
return
}
wasLeader := cc.isConsumerLeader(ca.Client.serviceAccount(), ca.Stream, ca.Name)
accName, stream, name, created := ca.Client.serviceAccount(), ca.Stream, ca.Name, ca.Created
wasLeader := cc.isConsumerLeader(accName, stream, name)
// Delete from our state.
var needDelete bool
if accStreams := cc.streams[ca.Client.serviceAccount()]; accStreams != nil {
if accStreams := cc.streams[accName]; accStreams != nil {
if sa := accStreams[ca.Stream]; sa != nil && sa.consumers != nil && sa.consumers[ca.Name] != nil {
oca := sa.consumers[ca.Name]
// Make sure this removal is for what we have, otherwise ignore.
@@ -5022,6 +5087,19 @@ func (js *jetStream) processConsumerRemoval(ca *consumerAssignment) {
}
js.mu.Unlock()
// During initial/startup recovery we'll not have registered the consumer assignment,
// but might have recovered the consumer from disk. We'll need to make sure that we only
// delete the consumer if it wasn't created after this delete.
if !needDelete && !created.IsZero() {
if acc, err := s.LookupAccount(accName); err == nil {
if mset, err := acc.lookupStream(stream); err == nil {
if o := mset.lookupConsumer(name); o != nil {
needDelete = !o.createdTime().After(created)
}
}
}
}
if needDelete {
js.processClusterDeleteConsumer(ca, wasLeader)
}
@@ -5351,7 +5429,7 @@ func (js *jetStream) processClusterDeleteConsumer(ca *consumerAssignment, wasLea
}
if err != nil {
resp.Error = NewJSStreamNotFoundError(Unless(err))
resp.Error = NewJSConsumerNotFoundError(Unless(err))
s.sendAPIErrResponse(ca.Client, acc, ca.Subject, ca.Reply, _EMPTY_, s.jsonResponse(resp))
} else {
resp.Success = true
@@ -5575,18 +5653,16 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) {
}
return
case <-qch:
// Clean signal from shutdown routine so do best effort attempt to snapshot.
// Don't snapshot if not shutting down, Raft node could be going away on a
// scale down or remove for example.
if s.isShuttingDown() {
doSnapshot(false)
}
// Raft node is closed, no use in trying to snapshot.
return
case <-aq.ch:
ces := aq.pop()
for _, ce := range ces {
// No special processing needed for when we are caught up on restart.
if ce == nil {
if !recovering {
continue
}
recovering = false
if n.NeedSnapshot() {
doSnapshot(true)
@@ -5675,14 +5751,12 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) {
stopMigrationMonitoring()
continue
}
newPeers, oldPeers, newPeerSet, _ := genPeerInfo(rg.Peers, len(rg.Peers)-replicas)
newPeers, _, newPeerSet, _ := genPeerInfo(rg.Peers, len(rg.Peers)-replicas)
// If we are part of the new peerset and we have been passed the baton.
// We will handle scale down.
if newPeerSet[ourPeerId] {
for _, p := range oldPeers {
n.ProposeRemovePeer(p)
}
n.ProposeKnownPeers(newPeers)
cca := ca.copyGroup()
cca.Group.Peers = newPeers
cca.Group.Cluster = s.cachedClusterName()
@@ -5716,6 +5790,12 @@ func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) {
func (js *jetStream) applyConsumerEntries(o *consumer, ce *CommittedEntry, isLeader bool) error {
for _, e := range ce.Entries {
// Ignore if lower-level catchup is started.
// We don't need to optimize during this, all entries are handled as normal.
if e.Type == EntryCatchup {
continue
}
if e.Type == EntrySnapshot {
if !isLeader {
// No-op needed?
@@ -6360,6 +6440,9 @@ func (js *jetStream) processLeaderChange(isLeader bool) {
js.mu.Lock()
defer js.mu.Unlock()
// Clear replies for peer-removes.
js.cluster.peerRemoveReply = nil
if isLeader {
if meta := js.cluster.meta; meta != nil && meta.IsObserver() {
meta.StepDown()
@@ -6385,7 +6468,7 @@ func (js *jetStream) processLeaderChange(isLeader bool) {
}
if sa.Sync == _EMPTY_ {
s.Warnf("Stream assignment corrupt for stream '%s > %s'", acc, sa.Config.Name)
nsa := &streamAssignment{Group: sa.Group, Config: sa.Config, Subject: sa.Subject, Reply: sa.Reply, Client: sa.Client}
nsa := &streamAssignment{Group: sa.Group, Config: sa.Config, Subject: sa.Subject, Reply: sa.Reply, Client: sa.Client, Created: sa.Created}
nsa.Sync = syncSubjForStream()
cc.meta.Propose(encodeUpdateStreamAssignment(nsa))
}
@@ -7500,7 +7583,7 @@ func (s *Server) jsClusteredStreamDeleteRequest(ci *ClientInfo, acc *Account, st
return
}
sa := &streamAssignment{Group: osa.Group, Config: osa.Config, Subject: subject, Reply: reply, Client: ci}
sa := &streamAssignment{Group: osa.Group, Config: osa.Config, Subject: subject, Reply: reply, Client: ci, Created: osa.Created}
cc.meta.Propose(encodeDeleteStreamAssignment(sa))
}
@@ -7975,7 +8058,7 @@ func (s *Server) jsClusteredConsumerDeleteRequest(ci *ClientInfo, acc *Account,
return
}
oca.deleted = true
ca := &consumerAssignment{Group: oca.Group, Stream: stream, Name: consumer, Config: oca.Config, Subject: subject, Reply: reply, Client: ci}
ca := &consumerAssignment{Group: oca.Group, Stream: stream, Name: consumer, Config: oca.Config, Subject: subject, Reply: reply, Client: ci, Created: oca.Created}
cc.meta.Propose(encodeDeleteConsumerAssignment(ca))
}
@@ -9210,6 +9293,10 @@ func (mset *stream) processSnapshot(snap *StreamReplicatedState, index uint64) (
mset.store.FastState(&state)
sreq := mset.calculateSyncRequest(&state, snap, index)
if mset.sa == nil || mset.node == nil {
mset.mu.Unlock()
return errCatchupStreamStopped
}
s, js, subject, n, st := mset.srv, mset.js, mset.sa.Sync, mset.node, mset.cfg.Storage
qname := fmt.Sprintf("[ACC:%s] stream '%s' snapshot", mset.acc.Name, mset.cfg.Name)
mset.mu.Unlock()
@@ -59,6 +59,9 @@ const (
// JSClusterRequiredErr JetStream clustering support required
JSClusterRequiredErr ErrorIdentifier = 10010
// JSClusterServerMemberChangeInflightErr cluster member change is in progress
JSClusterServerMemberChangeInflightErr ErrorIdentifier = 10202
// JSClusterServerNotMemberErr server is not a member of the cluster
JSClusterServerNotMemberErr ErrorIdentifier = 10044
@@ -626,6 +629,7 @@ var (
JSClusterNotLeaderErr: {Code: 500, ErrCode: 10009, Description: "JetStream cluster can not handle request"},
JSClusterPeerNotMemberErr: {Code: 400, ErrCode: 10040, Description: "peer not a member"},
JSClusterRequiredErr: {Code: 503, ErrCode: 10010, Description: "JetStream clustering support required"},
JSClusterServerMemberChangeInflightErr: {Code: 400, ErrCode: 10202, Description: "cluster member change is in progress"},
JSClusterServerNotMemberErr: {Code: 400, ErrCode: 10044, Description: "server is not a member of the cluster"},
JSClusterTagsErr: {Code: 400, ErrCode: 10011, Description: "tags placement not supported for operation"},
JSClusterUnSupportFeatureErr: {Code: 503, ErrCode: 10036, Description: "not currently supported in clustered mode"},
@@ -1031,6 +1035,16 @@ func NewJSClusterRequiredError(opts ...ErrorOption) *ApiError {
return ApiErrors[JSClusterRequiredErr]
}
// NewJSClusterServerMemberChangeInflightError creates a new JSClusterServerMemberChangeInflightErr error: "cluster member change is in progress"
func NewJSClusterServerMemberChangeInflightError(opts ...ErrorOption) *ApiError {
eopts := parseOpts(opts)
if ae, ok := eopts.err.(*ApiError); ok {
return ae
}
return ApiErrors[JSClusterServerMemberChangeInflightErr]
}
// NewJSClusterServerNotMemberError creates a new JSClusterServerNotMemberErr error: "server is not a member of the cluster"
func NewJSClusterServerNotMemberError(opts ...ErrorOption) *ApiError {
eopts := parseOpts(opts)
+1 -1
View File
@@ -2832,7 +2832,7 @@ func (c *client) processLeafSub(argo []byte) (err error) {
// Only add in shadow subs if a new sub or qsub.
if osub == nil {
if err := c.addShadowSubscriptions(acc, sub, true); err != nil {
if err := c.addShadowSubscriptions(acc, sub); err != nil {
c.Errorf(err.Error())
}
}
+41 -7
View File
@@ -861,17 +861,17 @@ func (ms *memStore) subjectsTotalsLocked(filterSubject string) map[string]uint64
}
// NumPending will return the number of pending messages matching the filter subject starting at sequence.
func (ms *memStore) NumPending(sseq uint64, filter string, lastPerSubject bool) (total, validThrough uint64) {
func (ms *memStore) NumPending(sseq uint64, filter string, lastPerSubject bool) (total, validThrough uint64, err error) {
// This needs to be a write lock, as filteredStateLocked can mutate the per-subject state.
ms.mu.Lock()
defer ms.mu.Unlock()
ss := ms.filteredStateLocked(sseq, filter, lastPerSubject)
return ss.Msgs, ms.state.LastSeq
return ss.Msgs, ms.state.LastSeq, nil
}
// NumPending will return the number of pending messages matching any subject in the sublist starting at sequence.
func (ms *memStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPerSubject bool) (total, validThrough uint64) {
func (ms *memStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPerSubject bool) (total, validThrough uint64, err error) {
if sl == nil {
return ms.NumPending(sseq, fwcs, lastPerSubject)
}
@@ -886,7 +886,7 @@ func (ms *memStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPerS
}
// If past the end no results.
if sseq > ms.state.LastSeq {
return 0, ms.state.LastSeq
return 0, ms.state.LastSeq, nil
}
update := func(fss *SimpleState) {
@@ -924,7 +924,7 @@ func (ms *memStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPerS
// If we did not encounter any partials we can return here.
if !havePartial {
return ss.Msgs, ms.state.LastSeq
return ss.Msgs, ms.state.LastSeq, nil
}
// If we are here we need to scan the msgs.
@@ -1015,7 +1015,7 @@ func (ms *memStore) NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPerS
ss.Msgs -= adjust
}
return ss.Msgs, ms.state.LastSeq
return ss.Msgs, ms.state.LastSeq, nil
}
// Will check the msg limit for this tracked subject.
@@ -1828,6 +1828,40 @@ func (ms *memStore) LoadPrevMsg(start uint64, smp *StoreMsg) (sm *StoreMsg, err
return nil, ErrStoreEOF
}
// LoadPrevMsgMulti will find the previous message matching any entry in the sublist.
func (ms *memStore) LoadPrevMsgMulti(sl *gsl.SimpleSublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error) {
// TODO(dlc) - for now simple linear walk to get started.
ms.mu.RLock()
defer ms.mu.RUnlock()
if start > ms.state.LastSeq {
start = ms.state.LastSeq
}
// If past the start no results.
if start < ms.state.FirstSeq || ms.state.Msgs == 0 {
return nil, ms.state.FirstSeq, ErrStoreEOF
}
// Initial setup.
fseq, lseq := start, ms.state.FirstSeq
for nseq := fseq; nseq >= lseq; nseq-- {
sm, ok := ms.msgs[nseq]
if !ok {
continue
}
if sl.HasInterest(sm.subj) {
if smp == nil {
smp = new(StoreMsg)
}
sm.copy(smp)
return smp, nseq, nil
}
}
return nil, ms.state.LastSeq, ErrStoreEOF
}
// RemoveMsg will remove the message from this store.
// Will return the number of bytes removed.
func (ms *memStore) RemoveMsg(seq uint64) (bool, error) {
@@ -2129,7 +2163,7 @@ type consumerMemStore struct {
closed bool
}
func (ms *memStore) ConsumerStore(name string, cfg *ConsumerConfig) (ConsumerStore, error) {
func (ms *memStore) ConsumerStore(name string, _ time.Time, cfg *ConsumerConfig) (ConsumerStore, error) {
if ms == nil {
return nil, fmt.Errorf("memstore is nil")
}
+13 -13
View File
@@ -500,31 +500,31 @@ func (s *Server) Connz(opts *ConnzOptions) (*Connz, error) {
switch sortOpt {
case ByCid, ByStart:
sort.Sort(byCid{pconns})
sort.Sort(SortByCid{pconns})
case BySubs:
sort.Sort(sort.Reverse(bySubs{pconns}))
sort.Sort(sort.Reverse(SortBySubs{pconns}))
case ByPending:
sort.Sort(sort.Reverse(byPending{pconns}))
sort.Sort(sort.Reverse(SortByPending{pconns}))
case ByOutMsgs:
sort.Sort(sort.Reverse(byOutMsgs{pconns}))
sort.Sort(sort.Reverse(SortByOutMsgs{pconns}))
case ByInMsgs:
sort.Sort(sort.Reverse(byInMsgs{pconns}))
sort.Sort(sort.Reverse(SortByInMsgs{pconns}))
case ByOutBytes:
sort.Sort(sort.Reverse(byOutBytes{pconns}))
sort.Sort(sort.Reverse(SortByOutBytes{pconns}))
case ByInBytes:
sort.Sort(sort.Reverse(byInBytes{pconns}))
sort.Sort(sort.Reverse(SortByInBytes{pconns}))
case ByLast:
sort.Sort(sort.Reverse(byLast{pconns}))
sort.Sort(sort.Reverse(SortByLast{pconns}))
case ByIdle:
sort.Sort(sort.Reverse(byIdle{pconns, c.Now}))
sort.Sort(sort.Reverse(SortByIdle{pconns, c.Now}))
case ByUptime:
sort.Sort(byUptime{pconns, time.Now()})
sort.Sort(SortByUptime{pconns, time.Now()})
case ByStop:
sort.Sort(sort.Reverse(byStop{pconns}))
sort.Sort(sort.Reverse(SortByStop{pconns}))
case ByReason:
sort.Sort(byReason{pconns})
sort.Sort(SortByReason{pconns})
case ByRTT:
sort.Sort(sort.Reverse(byRTT{pconns}))
sort.Sort(sort.Reverse(SortByRTT{pconns}))
}
minoff := c.Offset
+26 -26
View File
@@ -50,64 +50,64 @@ const (
// Individual sort options provide the Less for sort.Interface. Len and Swap are on cList.
// CID
type byCid struct{ ConnInfos }
type SortByCid struct{ ConnInfos }
func (l byCid) Less(i, j int) bool { return l.ConnInfos[i].Cid < l.ConnInfos[j].Cid }
func (l SortByCid) Less(i, j int) bool { return l.ConnInfos[i].Cid < l.ConnInfos[j].Cid }
// Number of Subscriptions
type bySubs struct{ ConnInfos }
type SortBySubs struct{ ConnInfos }
func (l bySubs) Less(i, j int) bool { return l.ConnInfos[i].NumSubs < l.ConnInfos[j].NumSubs }
func (l SortBySubs) Less(i, j int) bool { return l.ConnInfos[i].NumSubs < l.ConnInfos[j].NumSubs }
// Pending Bytes
type byPending struct{ ConnInfos }
type SortByPending struct{ ConnInfos }
func (l byPending) Less(i, j int) bool { return l.ConnInfos[i].Pending < l.ConnInfos[j].Pending }
func (l SortByPending) Less(i, j int) bool { return l.ConnInfos[i].Pending < l.ConnInfos[j].Pending }
// Outbound Msgs
type byOutMsgs struct{ ConnInfos }
type SortByOutMsgs struct{ ConnInfos }
func (l byOutMsgs) Less(i, j int) bool { return l.ConnInfos[i].OutMsgs < l.ConnInfos[j].OutMsgs }
func (l SortByOutMsgs) Less(i, j int) bool { return l.ConnInfos[i].OutMsgs < l.ConnInfos[j].OutMsgs }
// Inbound Msgs
type byInMsgs struct{ ConnInfos }
type SortByInMsgs struct{ ConnInfos }
func (l byInMsgs) Less(i, j int) bool { return l.ConnInfos[i].InMsgs < l.ConnInfos[j].InMsgs }
func (l SortByInMsgs) Less(i, j int) bool { return l.ConnInfos[i].InMsgs < l.ConnInfos[j].InMsgs }
// Outbound Bytes
type byOutBytes struct{ ConnInfos }
type SortByOutBytes struct{ ConnInfos }
func (l byOutBytes) Less(i, j int) bool { return l.ConnInfos[i].OutBytes < l.ConnInfos[j].OutBytes }
func (l SortByOutBytes) Less(i, j int) bool { return l.ConnInfos[i].OutBytes < l.ConnInfos[j].OutBytes }
// Inbound Bytes
type byInBytes struct{ ConnInfos }
type SortByInBytes struct{ ConnInfos }
func (l byInBytes) Less(i, j int) bool { return l.ConnInfos[i].InBytes < l.ConnInfos[j].InBytes }
func (l SortByInBytes) Less(i, j int) bool { return l.ConnInfos[i].InBytes < l.ConnInfos[j].InBytes }
// Last Activity
type byLast struct{ ConnInfos }
type SortByLast struct{ ConnInfos }
func (l byLast) Less(i, j int) bool {
func (l SortByLast) Less(i, j int) bool {
return l.ConnInfos[i].LastActivity.UnixNano() < l.ConnInfos[j].LastActivity.UnixNano()
}
// Idle time
type byIdle struct {
type SortByIdle struct {
ConnInfos
now time.Time
}
func (l byIdle) Less(i, j int) bool {
func (l SortByIdle) Less(i, j int) bool {
return l.now.Sub(l.ConnInfos[i].LastActivity) < l.now.Sub(l.ConnInfos[j].LastActivity)
}
// Uptime
type byUptime struct {
type SortByUptime struct {
ConnInfos
now time.Time
}
func (l byUptime) Less(i, j int) bool {
func (l SortByUptime) Less(i, j int) bool {
ci := l.ConnInfos[i]
cj := l.ConnInfos[j]
var upi, upj time.Duration
@@ -125,25 +125,25 @@ func (l byUptime) Less(i, j int) bool {
}
// Stop
type byStop struct{ ConnInfos }
type SortByStop struct{ ConnInfos }
func (l byStop) Less(i, j int) bool {
func (l SortByStop) Less(i, j int) bool {
ciStop := l.ConnInfos[i].Stop
cjStop := l.ConnInfos[j].Stop
return ciStop.Before(*cjStop)
}
// Reason
type byReason struct{ ConnInfos }
type SortByReason struct{ ConnInfos }
func (l byReason) Less(i, j int) bool {
func (l SortByReason) Less(i, j int) bool {
return l.ConnInfos[i].Reason < l.ConnInfos[j].Reason
}
// RTT - Default is descending
type byRTT struct{ ConnInfos }
type SortByRTT struct{ ConnInfos }
func (l byRTT) Less(i, j int) bool { return l.ConnInfos[i].rtt < l.ConnInfos[j].rtt }
func (l SortByRTT) Less(i, j int) bool { return l.ConnInfos[i].rtt < l.ConnInfos[j].rtt }
// IsValid determines if a sort option is valid
func (s SortOpt) IsValid() bool {
+396 -315
View File
File diff suppressed because it is too large Load Diff
+9 -2
View File
@@ -595,6 +595,11 @@ type WebsocketOpts struct {
// time needed for the TLS Handshake.
HandshakeTimeout time.Duration
// How often to send pings to WebSocket clients. When set to a non-zero
// duration, this overrides the default PingInterval for WebSocket connections.
// If not set or zero, the server's default PingInterval will be used.
PingInterval time.Duration
// Headers to be added to the upgrade response.
// Useful for adding custom headers like Strict-Transport-Security.
Headers map[string]string
@@ -1685,7 +1690,7 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin
case "reconnect_error_reports":
o.ReconnectErrorReports = int(v.(int64))
case "websocket", "ws":
if err := parseWebsocket(tk, o, errors); err != nil {
if err := parseWebsocket(tk, o, errors, warnings); err != nil {
*errors = append(*errors, err)
return
}
@@ -5313,7 +5318,7 @@ func parseStringArray(fieldName string, tk token, lt *token, mv any, errors *[]e
}
}
func parseWebsocket(v any, o *Options, errors *[]error) error {
func parseWebsocket(v any, o *Options, errors *[]error, warnings *[]error) error {
var lt token
defer convertPanicToErrorList(&lt, errors)
@@ -5414,6 +5419,8 @@ func parseWebsocket(v any, o *Options, errors *[]error) error {
o.Websocket.Headers[key] = headerValue
}
}
case "ping_interval":
o.Websocket.PingInterval = parseDuration("ping_interval", tk, mv, errors, warnings)
default:
if !tk.IsUsedVariable() {
err := &unknownConfigFieldErr{
+270 -115
View File
@@ -69,6 +69,7 @@ type RaftNode interface {
UpdateKnownPeers(knownPeers []string)
ProposeAddPeer(peer string) error
ProposeRemovePeer(peer string) error
MembershipChangeInProgress() bool
AdjustClusterSize(csz int) error
AdjustBootClusterSize(csz int) error
ClusterSize() int
@@ -229,6 +230,7 @@ type raft struct {
observer bool // The node is observing, i.e. not able to become leader
initializing bool // The node is new, and "empty log" checks can be temporarily relaxed.
scaleUp bool // The node is part of a scale up, puts us in observer mode until the log contains data.
membChanging bool // There is a membership change proposal in progress
}
type proposedEntry struct {
@@ -304,6 +306,7 @@ var (
errEntryLoadFailed = errors.New("raft: could not load entry from WAL")
errEntryStoreFailed = errors.New("raft: could not store entry to WAL")
errNodeClosed = errors.New("raft: node is closed")
errNodeRemoved = errors.New("raft: peer was removed")
errBadSnapName = errors.New("raft: snapshot name could not be parsed")
errNoSnapAvailable = errors.New("raft: no snapshot available")
errCatchupsRunning = errors.New("raft: snapshot can not be installed while catchups running")
@@ -315,6 +318,8 @@ var (
errTooManyEntries = errors.New("raft: append entry can contain a max of 64k entries")
errBadAppendEntry = errors.New("raft: append entry corrupt")
errNoInternalClient = errors.New("raft: no internal client")
errMembershipChange = errors.New("raft: membership change in progress")
errRemoveLastNode = errors.New("raft: cannot remove the last peer")
)
// This will bootstrap a raftNode by writing its config into the store directory.
@@ -919,66 +924,72 @@ func (n *raft) ForwardProposal(entry []byte) error {
// ProposeAddPeer is called to add a peer to the group.
func (n *raft) ProposeAddPeer(peer string) error {
n.RLock()
n.Lock()
// Check state under lock, we might not be leader anymore.
if n.State() != Leader {
n.RUnlock()
n.Unlock()
return errNotLeader
}
// Error if we had a previous write error.
if werr := n.werr; werr != nil {
n.RUnlock()
n.Unlock()
return werr
}
if n.membChanging {
n.Unlock()
return errMembershipChange
}
prop := n.prop
n.RUnlock()
n.membChanging = true
n.Unlock()
prop.push(newProposedEntry(newEntry(EntryAddPeer, []byte(peer)), _EMPTY_))
return nil
}
// As a leader if we are proposing to remove a peer assume its already gone.
func (n *raft) doRemovePeerAsLeader(peer string) {
n.Lock()
if n.removed == nil {
n.removed = map[string]time.Time{}
}
n.removed[peer] = time.Now()
if _, ok := n.peers[peer]; ok {
delete(n.peers, peer)
// We should decrease our cluster size since we are tracking this peer and the peer is most likely already gone.
n.adjustClusterSizeAndQuorum()
}
n.Unlock()
}
// ProposeRemovePeer is called to remove a peer from the group.
func (n *raft) ProposeRemovePeer(peer string) error {
n.RLock()
prop, subj := n.prop, n.rpsubj
isLeader := n.State() == Leader
werr := n.werr
n.RUnlock()
n.Lock()
// Error if we had a previous write error.
if werr != nil {
if werr := n.werr; werr != nil {
n.Unlock()
return werr
}
// If we are the leader then we are responsible for processing the
// peer remove and then notifying the rest of the group that the
// peer was removed.
if isLeader {
prop.push(newProposedEntry(newEntry(EntryRemovePeer, []byte(peer)), _EMPTY_))
n.doRemovePeerAsLeader(peer)
if n.State() != Leader {
subj := n.rpsubj
n.Unlock()
// Forward the proposal to the leader
n.sendRPC(subj, _EMPTY_, []byte(peer))
return nil
}
// Otherwise we need to forward the proposal to the leader.
n.sendRPC(subj, _EMPTY_, []byte(peer))
if n.membChanging {
n.Unlock()
return errMembershipChange
}
if len(n.peers) <= 1 {
n.Unlock()
return errRemoveLastNode
}
prop := n.prop
n.membChanging = true
n.Unlock()
prop.push(newProposedEntry(newEntry(EntryRemovePeer, []byte(peer)), _EMPTY_))
return nil
}
func (n *raft) MembershipChangeInProgress() bool {
n.RLock()
defer n.RUnlock()
return n.membChanging
}
// ClusterSize reports back the total cluster size.
// This effects quorum etc.
func (n *raft) ClusterSize() int {
@@ -1124,6 +1135,8 @@ func (n *raft) DrainAndReplaySnapshot() bool {
n.warn("Draining and replaying snapshot")
n.pauseApplyLocked()
n.apply.drain()
// Cancel after draining, we might have sent EntryCatchup and need to get them the nil entry.
n.cancelCatchup()
n.commit = snap.lastIndex
n.apply.push(newCommittedEntry(n.commit, []*Entry{{EntrySnapshot, snap.data}}))
return true
@@ -2378,6 +2391,10 @@ const (
EntryRemovePeer
EntryLeaderTransfer
EntrySnapshot
// EntryCatchup signals an internal type used to signal a Raft-level catchup has started.
// After the catchup completes (or is canceled), a nil entry will be sent to signal this.
// This type of entry is purely internal and not transmitted between peers or stored in the log.
EntryCatchup
)
func (t EntryType) String() string {
@@ -2405,6 +2422,15 @@ type Entry struct {
Data []byte
}
func (e *Entry) ChangesMembership() bool {
switch e.Type {
case EntryAddPeer, EntryRemovePeer:
return true
default:
return false
}
}
func (ae *appendEntry) String() string {
return fmt.Sprintf("&{leader:%s term:%d commit:%d pterm:%d pindex:%d entries: %d}",
ae.leader, ae.term, ae.commit, ae.pterm, ae.pindex, len(ae.entries))
@@ -2621,6 +2647,95 @@ func (n *raft) handleForwardedProposal(sub *subscription, c *client, _ *Account,
prop.push(newProposedEntry(newEntry(EntryNormal, msg), reply))
}
// Adds peer with the given id to our membership,
// and adjusts cluster size and quorum accordingly.
// Lock should be held.
func (n *raft) addPeer(peer string) {
// If we were on the removed list reverse that here.
if n.removed != nil {
delete(n.removed, peer)
}
if lp, ok := n.peers[peer]; !ok {
// We are not tracking this one automatically so we need
// to bump cluster size.
n.peers[peer] = &lps{time.Time{}, 0, true}
} else {
// Mark as added.
lp.kp = true
}
// Adjust cluster size and quorum if needed.
n.adjustClusterSizeAndQuorum()
// Write out our new state.
n.writePeerState(&peerState{n.peerNames(), n.csz, n.extSt})
}
// Remove the peer with the given id from our membership,
// and adjusts cluster size and quorum accordingly.
// Lock should be held.
func (n *raft) removePeer(peer string) {
if n.removed == nil {
n.removed = map[string]time.Time{}
}
n.removed[peer] = time.Now()
if _, ok := n.peers[peer]; ok {
delete(n.peers, peer)
n.adjustClusterSizeAndQuorum()
n.writePeerState(&peerState{n.peerNames(), n.csz, n.extSt})
}
}
// Build and send appendEntry request for the given entry that changes
// membership (EntryAddPeer / EntryRemovePeer).
// Returns true if the entry made it to the WAL and was sent to the followers
func (n *raft) sendMembershipChange(e *Entry) bool {
n.Lock()
defer n.Unlock()
// Only makes sense to call this with entries that change membership
if !e.ChangesMembership() {
return false
}
err := n.sendAppendEntryLocked([]*Entry{e}, true)
if err != nil {
n.membChanging = false
return false
}
if e.Type == EntryAddPeer {
n.addPeer(string(e.Data))
}
if e.Type == EntryRemovePeer {
n.removePeer(string(e.Data))
if n.csz == 1 {
n.tryCommit(n.pindex)
return true
}
}
return true
}
// logContainsUncommittedMembershipChange returns true if the
// log contains uncommitted entries that change membership.
// Lock should be held.
func (n *raft) logContainsUncommittedMembershipChange() (bool, error) {
for i := n.commit + 1; i <= n.pindex; i++ {
ae, err := n.loadEntry(i)
if err != nil {
return false, err
}
if len(ae.entries) > 0 && ae.entries[0].ChangesMembership() {
ae.returnToPool()
return true, nil
}
ae.returnToPool()
}
return false, nil
}
func (n *raft) runAsLeader() {
if n.State() == Closed {
return
@@ -2629,6 +2744,22 @@ func (n *raft) runAsLeader() {
n.Lock()
psubj, rpsubj := n.psubj, n.rpsubj
// Check if there are any uncommitted membership changes.
// If so, we need to make sure we don't propose any new
// ones until those are committed.
found, err := n.logContainsUncommittedMembershipChange()
if err != nil {
n.warn("Error while looking for membership changes in WAL: %v", err)
n.stepdownLocked(noLeader)
n.Unlock()
return
}
if found {
n.membChanging = true
n.debug("Log contains uncommitted membership change")
}
// For forwarded proposals, both normal and remove peer proposals.
fsub, err := n.subscribe(psubj, n.handleForwardedProposal)
if err != nil {
@@ -2680,8 +2811,9 @@ func (n *raft) runAsLeader() {
es, sz := n.prop.pop(), 0
for _, b := range es {
if b.Type == EntryRemovePeer {
n.doRemovePeerAsLeader(string(b.Data))
if b.ChangesMembership() {
n.sendMembershipChange(b.Entry)
continue
}
entries = append(entries, b.Entry)
// Increment size.
@@ -3059,8 +3191,19 @@ func (n *raft) applyCommit(index uint64) error {
n.commit = index
ae.buf = nil
var committed []*Entry
defer func() {
// Pass to the upper layers if we have normal entries. It is
// entirely possible that 'committed' might be an empty slice here,
// which will happen if we've processed updates inline (like peer
// states). In which case the upper layer will just call down with
// Applied() with no further action.
n.apply.push(newCommittedEntry(index, committed))
// Place back in the pool.
ae.returnToPool()
}()
for _, e := range ae.entries {
switch e.Type {
case EntryNormal:
@@ -3092,82 +3235,78 @@ func (n *raft) applyCommit(index uint64) error {
// Store our peer in our global peer map for all peers.
peers.LoadOrStore(newPeer, newPeer)
// If we were on the removed list reverse that here.
if n.removed != nil {
delete(n.removed, newPeer)
}
n.addPeer(newPeer)
if lp, ok := n.peers[newPeer]; !ok {
// We are not tracking this one automatically so we need to bump cluster size.
n.peers[newPeer] = &lps{time.Time{}, 0, true}
} else {
// Mark as added.
lp.kp = true
}
// Adjust cluster size and quorum if needed.
n.adjustClusterSizeAndQuorum()
// Write out our new state.
n.writePeerState(&peerState{n.peerNames(), n.csz, n.extSt})
// We pass these up as well.
committed = append(committed, e)
// We are done with this membership change
n.membChanging = false
case EntryRemovePeer:
peer := string(e.Data)
n.debug("Removing peer %q", peer)
// Make sure we have our removed map.
if n.removed == nil {
n.removed = make(map[string]time.Time)
}
n.removed[peer] = time.Now()
if _, ok := n.peers[peer]; ok {
delete(n.peers, peer)
// We should decrease our cluster size since we are tracking this peer.
n.adjustClusterSizeAndQuorum()
// Write out our new state.
n.writePeerState(&peerState{n.peerNames(), n.csz, n.extSt})
}
// If this is us and we are the leader we should attempt to stepdown.
if peer == n.id && n.State() == Leader {
n.stepdownLocked(n.selectNextLeader())
}
n.removePeer(peer)
// Remove from string intern map.
peers.Delete(peer)
// We pass these up as well.
committed = append(committed, e)
// We are done with this membership change
n.membChanging = false
// If this is us and we are the leader signal the caller
// to attempt to stepdown.
if peer == n.id && n.State() == Leader {
return errNodeRemoved
}
}
}
// Pass to the upper layers if we have normal entries. It is
// entirely possible that 'committed' might be an empty slice here,
// which will happen if we've processed updates inline (like peer
// states). In which case the upper layer will just call down with
// Applied() with no further action.
n.apply.push(newCommittedEntry(index, committed))
// Place back in the pool.
ae.returnToPool()
return nil
}
// Used to track a success response and apply entries.
func (n *raft) trackResponse(ar *appendEntryResponse) {
if n.State() == Closed {
return
// Check if there is a quorum for the given index, and if
// so, commit the corresponding entry.
// Return true if the index was committed, false otherwise.
// Lock should be held.
func (n *raft) tryCommit(index uint64) (bool, error) {
acks := len(n.acks[index])
// Count the leader if it's still part of membership
if n.peers[n.ID()] != nil {
acks += 1
}
if acks < n.qn {
return false, nil
}
// We have a quorum
for i := n.commit + 1; i <= index; i++ {
if err := n.applyCommit(i); err != nil {
if err != errNodeClosed && err != errNodeRemoved {
n.error("Got an error applying commit for %d: %v", i, err)
}
return false, err
}
}
return true, nil
}
n.Lock()
// Used to track a success response. Returns true if the
// response was tracked, false if the response was ignored
// (the response is old, the index is already committed, ...)
// Lock should be held.
func (n *raft) trackResponse(ar *appendEntryResponse) bool {
// Check state under lock, we might not be leader anymore.
if n.State() != Leader {
n.Unlock()
return
return false
}
ps := n.peers[ar.peer]
// Update peer's last index.
if ps := n.peers[ar.peer]; ps != nil && ar.index > ps.li {
if ps != nil && ar.index > ps.li {
ps.li = ar.index
}
@@ -3178,13 +3317,15 @@ func (n *raft) trackResponse(ar *appendEntryResponse) {
// Ignore items already committed.
if ar.index <= n.commit {
n.Unlock()
return
return false
}
// See if we have items to apply.
var sendHB bool
// Not a peer, can't count this message towards quorum
if ps == nil {
return false
}
// Keep track of the response
results := n.acks[ar.index]
if results == nil {
results = make(map[string]struct{})
@@ -3192,22 +3333,7 @@ func (n *raft) trackResponse(ar *appendEntryResponse) {
}
results[ar.peer] = struct{}{}
// We don't count ourselves to account for leader changes, so add 1.
if nr := len(results); nr+1 >= n.qn {
// We have a quorum.
for index := n.commit + 1; index <= ar.index; index++ {
if err := n.applyCommit(index); err != nil && err != errNodeClosed {
n.error("Got an error applying commit for %d: %v", index, err)
break
}
}
sendHB = n.prop.len() == 0
}
n.Unlock()
if sendHB {
n.sendHeartbeat()
}
return true
}
// Used to adjust cluster size and peer count based on added official peers.
@@ -3256,8 +3382,6 @@ func (n *raft) trackPeer(peer string) error {
}
if ps := n.peers[peer]; ps != nil {
ps.ts = time.Now()
} else if !isRemoved {
n.peers[peer] = &lps{time.Now(), 0, false}
}
n.Unlock()
@@ -3374,6 +3498,8 @@ func (n *raft) cancelCatchup() {
if n.catchup != nil && n.catchup.sub != nil {
n.unsubscribe(n.catchup.sub)
// Send nil entry to signal the upper layers we are done catching up.
n.apply.push(nil)
}
n.catchup = nil
}
@@ -3401,6 +3527,9 @@ func (n *raft) createCatchup(ae *appendEntry) string {
// Cleanup any old ones.
if n.catchup != nil && n.catchup.sub != nil {
n.unsubscribe(n.catchup.sub)
} else {
// Signal to the upper layer that the following entries are catchup entries, up until the nil guard.
n.apply.push(newCommittedEntry(0, []*Entry{{EntryCatchup, nil}}))
}
// Snapshot term and index.
n.catchup = &catchupState{
@@ -3677,8 +3806,6 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) {
if isNew && ae.leader != noLeader && ae.leader == n.leader {
if ps := n.peers[ae.leader]; ps != nil {
ps.ts = time.Now()
} else {
n.peers[ae.leader] = &lps{time.Now(), 0, true}
}
}
@@ -3937,7 +4064,29 @@ func (n *raft) processAppendEntryResponse(ar *appendEntryResponse) {
// The remote node successfully committed the append entry.
// They agree with our leadership and are happy with the state of the log.
// In this case ar.term doesn't matter.
n.trackResponse(ar)
var err error
var committed bool
n.Lock()
if n.trackResponse(ar) {
committed, err = n.tryCommit(ar.index)
}
n.Unlock()
// Leader was peer-removed. Attempt a step-down to
// a new leader before shutting down.
if err == errNodeRemoved {
n.StepDown()
n.Stop()
}
// Send a heartbeat if there is no other message lined
// up, so that followers can apply without waiting for
// the next message.
if committed && n.prop.len() == 0 {
n.sendHeartbeat()
}
arPool.Put(ar)
} else if ar.reply != _EMPTY_ {
// The remote node didn't commit the append entry, and they believe they
@@ -4030,12 +4179,15 @@ func (n *raft) sendAppendEntry(entries []*Entry) {
defer n.Unlock()
n.sendAppendEntryLocked(entries, true)
}
func (n *raft) sendAppendEntryLocked(entries []*Entry, checkLeader bool) {
// Returns nil if an appendEntry was appended to our WAL and sent to followers,
// an error otherwise.
func (n *raft) sendAppendEntryLocked(entries []*Entry, checkLeader bool) error {
// Safeguard against sending an append entry right after a stepdown from a different goroutine.
// Specifically done while holding the lock to not race.
if checkLeader && n.State() != Leader {
n.debug("Not sending append entry, not leader")
return
return errNotLeader
}
ae := n.buildAppendEntry(entries)
@@ -4043,14 +4195,14 @@ func (n *raft) sendAppendEntryLocked(entries []*Entry, checkLeader bool) {
var scratch [1024]byte
ae.buf, err = ae.encode(scratch[:])
if err != nil {
return
return err
}
// If we have entries store this in our wal.
shouldStore := ae.shouldStore()
if shouldStore {
if err := n.storeToWAL(ae); err != nil {
return
return err
}
n.active = time.Now()
n.cachePendingEntry(ae)
@@ -4059,6 +4211,7 @@ func (n *raft) sendAppendEntryLocked(entries []*Entry, checkLeader bool) {
if !shouldStore {
ae.returnToPool()
}
return nil
}
// cachePendingEntry saves append entries in memory for faster processing during applyCommit.
@@ -4590,6 +4743,8 @@ func (n *raft) switchToFollowerLocked(leader string) {
n.leaderState.Store(false)
n.leaderSince.Store(nil)
n.lxfer = false
n.membChanging = false
// Reset acks, we can't assume acks from a previous term are still valid in another term.
if len(n.acks) > 0 {
n.acks = make(map[uint64]map[string]struct{})
+4 -3
View File
@@ -2126,9 +2126,6 @@ func (s *Server) reloadAuthorization() {
resetCh <- struct{}{}
}
// Check that publish retained messages sources are still allowed to publish.
s.mqttCheckPubRetainedPerms()
// Close clients that have moved accounts
for _, client := range cclients {
client.closeConnection(ClientClosed)
@@ -2168,6 +2165,10 @@ func (s *Server) reloadAuthorization() {
s.Errorf(err.Error())
}
}
// Check that publish retained messages sources are still allowed to publish.
// Do this after dealing with JetStream.
s.mqttCheckPubRetainedPerms()
}
// Returns true if given client current account has changed (or user
+4 -3
View File
@@ -99,6 +99,7 @@ type StreamStore interface {
LoadNextMsgMulti(sl *gsl.SimpleSublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error)
LoadLastMsg(subject string, sm *StoreMsg) (*StoreMsg, error)
LoadPrevMsg(start uint64, smp *StoreMsg) (sm *StoreMsg, err error)
LoadPrevMsgMulti(sl *gsl.SimpleSublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error)
RemoveMsg(seq uint64) (bool, error)
EraseMsg(seq uint64) (bool, error)
Purge() (uint64, error)
@@ -112,8 +113,8 @@ type StreamStore interface {
AllLastSeqs() ([]uint64, error)
MultiLastSeqs(filters []string, maxSeq uint64, maxAllowed int) ([]uint64, error)
SubjectForSeq(seq uint64) (string, error)
NumPending(sseq uint64, filter string, lastPerSubject bool) (total, validThrough uint64)
NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPerSubject bool) (total, validThrough uint64)
NumPending(sseq uint64, filter string, lastPerSubject bool) (total, validThrough uint64, err error)
NumPendingMulti(sseq uint64, sl *gsl.SimpleSublist, lastPerSubject bool) (total, validThrough uint64, err error)
State() StreamState
FastState(*StreamState)
EncodedStreamState(failed uint64) (enc []byte, err error)
@@ -125,7 +126,7 @@ type StreamStore interface {
UpdateConfig(cfg *StreamConfig) error
Delete(inline bool) error
Stop() error
ConsumerStore(name string, cfg *ConsumerConfig) (ConsumerStore, error)
ConsumerStore(name string, created time.Time, cfg *ConsumerConfig) (ConsumerStore, error)
AddConsumer(o ConsumerStore) error
RemoveConsumer(o ConsumerStore) error
Snapshot(deadline time.Duration, includeConsumers, checkMsgs bool) (*SnapshotResult, error)
+100 -20
View File
@@ -827,11 +827,19 @@ func (a *Account) addStreamWithAssignment(config *StreamConfig, fsConfig *FileSt
ipqLimitByLen[*inMsg](mlen),
ipqLimitBySize[*inMsg](msz),
),
gets: newIPQueue[*directGetReq](s, qpfx+"direct gets"),
qch: make(chan struct{}),
mqch: make(chan struct{}),
uch: make(chan struct{}, 4),
sch: make(chan struct{}, 1),
gets: newIPQueue[*directGetReq](s, qpfx+"direct gets"),
qch: make(chan struct{}),
mqch: make(chan struct{}),
uch: make(chan struct{}, 4),
sch: make(chan struct{}, 1),
created: time.Now().UTC(),
}
// Add created timestamp used for the store, must match that of the stream assignment if it exists.
if sa != nil {
js.mu.RLock()
mset.created = sa.Created
js.mu.RUnlock()
}
// Start our signaling routine to process consumers.
@@ -895,7 +903,6 @@ func (a *Account) addStreamWithAssignment(config *StreamConfig, fsConfig *FileSt
fsCfg.SyncAlways = false
fsCfg.AsyncFlush = true
}
if err := mset.setupStore(fsCfg); err != nil {
mset.stop(true, false)
return nil, NewJSStreamStoreFailedError(err)
@@ -4086,28 +4093,61 @@ func (mset *stream) setStartingSequenceForSources(iNames map[string]struct{}) {
return
}
// From the provided list of sources, we build a sublist that contains
// the interested filters (including transforms). As we figure out the
// starting sequence for each source, we will eliminate the source from
// the map and then refresh the sublist, which in turn makes the sublist
// ideally more specific. This allows LoadPrevMsgsMulti to work most
// effectively.
// Because this is a SimpleSublist we can't just remove the entries per
// source so we have no other option but to rebuild it from scratch, but
// this is cheap enough to do so not the end of the world.
var sl *gsl.SimpleSublist
refreshSublist := func() {
sl = gsl.NewSimpleSublist()
for iName := range iNames {
si := mset.sources[iName]
if si == nil {
continue
}
if si.sf == _EMPTY_ {
sl.Insert(fwcs, struct{}{})
} else {
sl.Insert(si.sf, struct{}{})
}
for _, sf := range si.sfs {
if sf == _EMPTY_ {
sl.Insert(fwcs, struct{}{})
} else {
sl.Insert(sf, struct{}{})
}
}
}
}
refreshSublist()
var smv StoreMsg
for seq := state.LastSeq; seq >= state.FirstSeq; {
sm, err := mset.store.LoadPrevMsg(seq, &smv)
for last := state.LastSeq; ; {
sm, seq, err := mset.store.LoadPrevMsgMulti(sl, last, &smv)
if err == ErrStoreEOF || err != nil {
break
}
seq = sm.seq - 1
last = seq - 1
if len(sm.hdr) == 0 {
continue
}
ss := getHeader(JSStreamSource, sm.hdr)
ss := sliceHeader(JSStreamSource, sm.hdr)
if len(ss) == 0 {
continue
}
streamName, indexName, sseq := streamAndSeq(bytesToString(ss))
streamName, indexName, sseq := streamAndSeq(bytesToString(ss))
if _, ok := iNames[indexName]; ok {
si := mset.sources[indexName]
si.sseq = sseq
si.dseq = 0
delete(iNames, indexName)
refreshSublist()
} else if indexName == _EMPTY_ && streamName != _EMPTY_ {
for iName := range iNames {
// TODO streamSource is a linear walk, to optimize later
@@ -4116,6 +4156,7 @@ func (mset *stream) setStartingSequenceForSources(iNames map[string]struct{}) {
si.sseq = sseq
si.dseq = 0
delete(iNames, iName)
refreshSublist()
break
}
}
@@ -4197,26 +4238,61 @@ func (mset *stream) startingSequenceForSources() {
}
}()
// Generate a list of sources and, from that, a sublist that contains
// the interested filters (including transforms). As we figure out the
// starting sequence for each source, we will eliminate the source from
// the map and then refresh the sublist, which in turn makes the sublist
// ideally more specific. This allows LoadPrevMsgsMulti to work most
// effectively.
// Because this is a SimpleSublist we can't just remove the entries per
// source so we have no other option but to rebuild it from scratch, but
// this is cheap enough to do so not the end of the world.
sources := map[string]*StreamSource{}
for _, src := range mset.cfg.Sources {
sources[src.composeIName()] = src
}
var sl *gsl.SimpleSublist
refreshSublist := func() {
sl = gsl.NewSimpleSublist()
for _, src := range sources {
if src.FilterSubject == _EMPTY_ {
sl.Insert(fwcs, struct{}{})
} else {
sl.Insert(src.FilterSubject, struct{}{})
}
for _, tr := range src.SubjectTransforms {
if tr.Destination == _EMPTY_ {
sl.Insert(fwcs, struct{}{})
} else {
sl.Insert(tr.Destination, struct{}{})
}
}
}
}
refreshSublist()
update := func(iName string, seq uint64) {
// Only update active in case we have older ones in here that got configured out.
if si := mset.sources[iName]; si != nil {
if _, ok := seqs[iName]; !ok {
seqs[iName] = seq
delete(sources, iName)
refreshSublist()
}
}
}
var smv StoreMsg
for seq := state.LastSeq; ; {
sm, err := mset.store.LoadPrevMsg(seq, &smv)
for last := state.LastSeq; ; {
sm, seq, err := mset.store.LoadPrevMsgMulti(sl, last, &smv)
if err == ErrStoreEOF || err != nil {
break
}
seq = sm.seq - 1
last = seq - 1
if len(sm.hdr) == 0 {
continue
}
ss := getHeader(JSStreamSource, sm.hdr)
ss := sliceHeader(JSStreamSource, sm.hdr)
if len(ss) == 0 {
continue
}
@@ -4546,8 +4622,6 @@ func (mset *stream) unsubscribe(sub *subscription) {
func (mset *stream) setupStore(fsCfg *FileStoreConfig) error {
mset.mu.Lock()
mset.created = time.Now().UTC()
switch mset.cfg.Storage {
case MemoryStorage:
ms, err := newMemStore(&mset.cfg)
@@ -5208,7 +5282,10 @@ func (mset *stream) getDirectRequest(req *JSApiMsgGetRequest, reply string) {
} else {
// This is a batch request, capture initial numPending.
isBatchRequest = true
np, validThrough = store.NumPending(seq, req.NextFor, false)
var err error
if np, validThrough, err = store.NumPending(seq, req.NextFor, false); err != nil {
return
}
}
// Grab MaxBytes
@@ -5301,7 +5378,10 @@ func (mset *stream) getDirectRequest(req *JSApiMsgGetRequest, reply string) {
if isBatchRequest {
// Update if the stream's last sequence has moved past our validThrough.
if mset.lseq > validThrough {
np, _ = store.NumPending(seq, req.NextFor, false)
var err error
if np, _, err = store.NumPending(seq, req.NextFor, false); err != nil {
return
}
}
hdr := fmt.Appendf(nil, eob, np, lseq)
mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
+4 -1
View File
@@ -36,7 +36,6 @@ func genParts(filter []byte, parts [][]byte) [][]byte {
}
start = i + 1
} else if i < e && filter[i+1] == fwc && i+1 == e {
// We have a fwc
if i > start {
parts = append(parts, filter[start:i+1])
}
@@ -53,6 +52,10 @@ func genParts(filter []byte, parts [][]byte) [][]byte {
if next := i + 1; next == e || next < e && filter[next] != tsep {
continue
}
// Full wildcard must be terminal.
if filter[i] == fwc && i < e {
break
}
// We start with a pwc or fwc.
parts = append(parts, filter[i:i+1])
if i+1 <= e {
+22 -4
View File
@@ -31,6 +31,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"unicode/utf8"
@@ -211,6 +212,7 @@ func (c *client) wsRead(r *wsReadInfo, ior io.Reader, buf []byte) ([][]byte, err
err error
pos int
max = len(buf)
mpay = int(atomic.LoadInt32(&c.mpay))
)
for pos != max {
if r.fs {
@@ -324,7 +326,7 @@ func (c *client) wsRead(r *wsReadInfo, ior io.Reader, buf []byte) ([][]byte, err
// When we have the final frame and we have read the full payload,
// we can decompress it.
if r.ff && r.rem == 0 {
b, err = r.decompress()
b, err = r.decompress(mpay)
if err != nil {
return bufs, err
}
@@ -398,7 +400,16 @@ func (r *wsReadInfo) ReadByte() (byte, error) {
return b, nil
}
func (r *wsReadInfo) decompress() ([]byte, error) {
// decompress decompresses the collected buffers.
// The size of the decompressed buffer will be limited to the `mpay` value.
// If, while decompressing, the resulting uncompressed buffer exceeds this
// limit, the decompression stops and an empty buffer and the ErrMaxPayload
// error are returned.
func (r *wsReadInfo) decompress(mpay int) ([]byte, error) {
// If not limit is specified, use the default maximum payload size.
if mpay <= 0 {
mpay = MAX_PAYLOAD_SIZE
}
r.coff = 0
// As per https://tools.ietf.org/html/rfc7692#section-7.2.2
// add 0x00, 0x00, 0xff, 0xff and then a final block so that flate reader
@@ -413,8 +424,15 @@ func (r *wsReadInfo) decompress() ([]byte, error) {
} else {
d.(flate.Resetter).Reset(r, nil)
}
// This will do the decompression.
b, err := io.ReadAll(d)
// Use a LimitedReader to limit the decompressed size.
// We use "limit+1" bytes for "N" so we can detect if the limit is exceeded.
lr := io.LimitedReader{R: d, N: int64(mpay + 1)}
b, err := io.ReadAll(&lr)
if err == nil && len(b) > mpay {
// Decompressed data exceeds the maximum payload size.
b, err = nil, ErrMaxPayload
}
lr.R = nil
decompressorPool.Put(d)
// Now reset the compressed buffers list.
r.cbufs = nil
@@ -223,6 +223,7 @@ func (h *Handler) Init(c *config.Config) {
Edition: "",
Product: "reva",
ProductVersion: "",
Channel: "",
}
}
@@ -151,6 +151,7 @@ type Status struct {
Product string `json:"product" xml:"product"`
ProductVersion string `json:"productversion" xml:"productversion"`
Hostname string `json:"hostname,omitempty" xml:"hostname,omitempty"`
Channel string `json:"channel" xml:"channel"`
}
// CapabilitiesChecksums holds available hashes
@@ -321,4 +322,5 @@ type Version struct {
Edition string `json:"edition" xml:"edition"`
Product string `json:"product" xml:"product"`
ProductVersion string `json:"productversion" xml:"productversion"`
Channel string `json:"channel" xml:"channel"`
}
+21 -1
View File
@@ -20,9 +20,11 @@ package tus
import (
"context"
"fmt"
"net/http"
"path"
"regexp"
"runtime"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/mitchellh/mapstructure"
@@ -253,6 +255,13 @@ func (l tusdLogger) Handle(_ context.Context, r slog.Record) error {
case slog.LevelError:
logev = l.log.Error()
}
// Extract caller information from slog.Record only for debug and info levels
if (r.Level == slog.LevelDebug || r.Level == slog.LevelInfo) && r.PC != 0 {
frames := runtime.CallersFrames([]uintptr{r.PC})
frame, _ := frames.Next()
// add line using zerolog's caller format
logev = logev.Str("line", fmt.Sprintf("%s:%d", frame.File, frame.Line))
}
r.Attrs(func(a slog.Attr) bool {
// Resolve the Attr's value before doing anything else.
a.Value = a.Value.Resolve()
@@ -281,7 +290,18 @@ func (l tusdLogger) Enabled(_ context.Context, _ slog.Level) bool { return true
func (l tusdLogger) WithAttrs(attr []slog.Attr) slog.Handler {
fields := make(map[string]interface{}, len(attr))
for _, a := range attr {
fields[a.Key] = a.Value.String()
switch a.Value.Kind() {
case slog.KindBool:
fields[a.Key] = a.Value.Bool()
case slog.KindInt64:
fields[a.Key] = a.Value.Int64()
case slog.KindUint64:
fields[a.Key] = a.Value.Uint64()
case slog.KindFloat64:
fields[a.Key] = a.Value.Float64()
default:
fields[a.Key] = a.Value.String()
}
}
c := l.log.With().Fields(fields).Logger()
sLog := tusdLogger{log: &c}
+15 -9
View File
@@ -44,15 +44,18 @@ var (
// Config contains the configuring for a cache
type Config struct {
Store string `mapstructure:"cache_store"`
Nodes []string `mapstructure:"cache_nodes"`
Database string `mapstructure:"cache_database"`
Table string `mapstructure:"cache_table"`
TTL time.Duration `mapstructure:"cache_ttl"`
Size int `mapstructure:"cache_size"`
DisablePersistence bool `mapstructure:"cache_disable_persistence"`
AuthUsername string `mapstructure:"cache_auth_username"`
AuthPassword string `mapstructure:"cache_auth_password"`
Store string `mapstructure:"cache_store"`
Nodes []string `mapstructure:"cache_nodes"`
Database string `mapstructure:"cache_database"`
Table string `mapstructure:"cache_table"`
TTL time.Duration `mapstructure:"cache_ttl"`
Size int `mapstructure:"cache_size"`
DisablePersistence bool `mapstructure:"cache_disable_persistence"`
AuthUsername string `mapstructure:"cache_auth_username"`
AuthPassword string `mapstructure:"cache_auth_password"`
TLSEnabled bool `mapstructure:"cache_tls_enabled"`
TLSInsecure bool `mapstructure:"cache_tls_insecure"`
TLSRootCACertificate string `mapstructure:"cache_tls_root_ca_certificate"`
}
// Cache handles key value operations on caches
@@ -243,5 +246,8 @@ func getStore(cfg Config) microstore.Store {
store.Size(cfg.Size),
store.DisablePersistence(cfg.DisablePersistence),
store.Authentication(cfg.AuthUsername, cfg.AuthPassword),
store.TLSEnabled(cfg.TLSEnabled),
store.TLSInsecure(cfg.TLSInsecure),
store.TLSRootCA(cfg.TLSRootCACertificate),
)
}
@@ -38,12 +38,16 @@ func NewStoreIDCache(c cache.Config) *StoreIDCache {
return &StoreIDCache{
cache: store.Create(
store.Store(c.Store),
store.TTL(c.TTL),
store.Size(c.Size),
microstore.Nodes(c.Nodes...),
microstore.Database(c.Database),
microstore.Table(c.Table),
store.DisablePersistence(c.DisablePersistence),
store.Authentication(c.AuthUsername, c.AuthPassword),
store.TLSEnabled(c.TLSEnabled),
store.TLSInsecure(c.TLSInsecure),
store.TLSRootCA(c.TLSRootCACertificate),
),
}
}
+4
View File
@@ -131,6 +131,7 @@ func New(m map[string]interface{}, stream events.Stream, log *zerolog.Logger) (s
}
tp, err := tree.New(lu, bs, um, trashbin, p, o, stream, store.Create(
// TODO use a NewStoreIDCache here?
store.Store(o.IDCache.Store),
store.TTL(o.IDCache.TTL),
store.Size(o.IDCache.Size),
@@ -139,6 +140,9 @@ func New(m map[string]interface{}, stream events.Stream, log *zerolog.Logger) (s
microstore.Table(o.IDCache.Table),
store.DisablePersistence(o.IDCache.DisablePersistence),
store.Authentication(o.IDCache.AuthUsername, o.IDCache.AuthPassword),
store.TLSEnabled(o.IDCache.TLSEnabled),
store.TLSInsecure(o.IDCache.TLSInsecure),
store.TLSRootCA(o.IDCache.TLSRootCACertificate),
), log)
if err != nil {
return nil, err
@@ -38,6 +38,7 @@ import (
"github.com/rs/zerolog/log"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/watcher"
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata"
@@ -60,7 +61,7 @@ type queueItem struct {
timer *time.Timer
}
const dirtyFlag = "user.oc.dirty"
const dirtyFlag = prefixes.OcPrefix + "dirty"
type assimilationNode struct {
path string
@@ -366,6 +367,11 @@ func (t *Tree) findSpaceId(path string) (string, error) {
// find the space id, scope by the according user
spaceCandidate := path
for strings.HasPrefix(spaceCandidate, t.options.Root) {
// jail at root
if t.isRootPath(spaceCandidate) {
return "", ErrRootReached
}
spaceID, _, err := t.lookup.IDsForPath(context.Background(), spaceCandidate)
if err == nil && len(spaceID) > 0 {
if t.options.UseSpaceGroups {
@@ -412,7 +418,11 @@ func (t *Tree) assimilate(item scanItem) error {
if spaceID == "" {
// node didn't have a space ID attached. try to find it by walking up the path on disk
spaceID, err = t.findSpaceId(filepath.Dir(item.Path))
if err != nil {
switch {
// ignore if we reached the root without finding a space
case errors.Is(err, ErrRootReached):
return nil
case err != nil:
return err
}
}
@@ -741,13 +751,17 @@ assimilate:
t.log.Error().Err(err).Str("path", path).Str("currentPath", currentPath).Msg("could not open current path for writing")
return
}
defer w.Close()
defer func() {
_ = w.Close()
}()
r, err := os.OpenFile(path, os.O_RDONLY, 0600)
if err != nil {
t.log.Error().Err(err).Str("path", path).Msg("could not open file for reading")
return
}
defer r.Close()
defer func() {
_ = r.Close()
}()
_, err = io.Copy(w, r)
if err != nil {
+18 -5
View File
@@ -59,7 +59,12 @@ import (
"github.com/opencloud-eu/reva/v2/pkg/utils"
)
var tracer trace.Tracer
var (
tracer trace.Tracer
// ErrRootReached is returned when the root of the tree is reached
ErrRootReached = errors.New("root of the tree reached")
)
func init() {
tracer = otel.Tracer("github.com/cs3org/reva/pkg/storage/pkg/decomposedfs/tree")
@@ -244,7 +249,7 @@ func (t *Tree) Setup() error {
}
// GetMD returns the metadata of a node in the tree
func (t *Tree) GetMD(ctx context.Context, n *node.Node) (os.FileInfo, error) {
func (t *Tree) GetMD(_ context.Context, n *node.Node) (os.FileInfo, error) {
md, err := os.Stat(n.InternalPath())
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
@@ -295,6 +300,9 @@ func (t *Tree) TouchFile(ctx context.Context, n *node.Node, markprocessing bool,
if err != nil {
return errors.Wrap(err, "posixfs: error creating node")
}
defer func() {
_ = f.Close()
}()
attributes := n.NodeMetadata(ctx)
attributes[prefixes.IDAttr] = []byte(n.ID)
@@ -452,7 +460,9 @@ func (t *Tree) ListFolder(ctx context.Context, n *node.Node) ([]*node.Node, erro
}
return nil, errors.Wrap(err, "tree: error listing "+dir)
}
defer f.Close()
defer func() {
_ = f.Close()
}()
_, subspan = tracer.Start(ctx, "f.Readdirnames")
names, err := f.Readdirnames(0)
@@ -685,7 +695,7 @@ func (t *Tree) InitNewNode(ctx context.Context, n *node.Node, fsize uint64) (met
}
return unlock, err
}
h.Close()
_ = h.Close()
if _, err := node.CheckQuota(ctx, n.SpaceRoot, false, 0, fsize); err != nil {
return unlock, err
@@ -727,9 +737,12 @@ func (t *Tree) createDirNode(ctx context.Context, n *node.Node) (err error) {
// Write mtime from filesystem to metadata to preven re-assimilation
d, err := os.Open(path)
if err != nil {
return err
}
defer func() {
_ = d.Close()
}()
fi, err := d.Stat()
if err != nil {
return err
@@ -169,6 +169,9 @@ func NewDefault(m map[string]interface{}, bs node.Blobstore, es events.Stream, l
microstore.Table(o.IDCache.Table),
store.DisablePersistence(o.IDCache.DisablePersistence),
store.Authentication(o.IDCache.AuthUsername, o.IDCache.AuthPassword),
store.TLSEnabled(o.IDCache.TLSEnabled),
store.TLSInsecure(o.IDCache.TLSInsecure),
store.TLSRootCA(o.IDCache.TLSRootCACertificate),
), log)
aspects := aspects.Aspects{
@@ -591,6 +591,17 @@ func (n *Node) readOwner(ctx context.Context) (*userpb.UserId, error) {
return nil, err
}
// lookup Tenant in extended attributes
attr, err = n.SpaceRoot.XattrString(ctx, prefixes.SpaceTenantIDAttr)
switch {
case err == nil:
owner.TenantId = attr
case metadata.IsAttrUnset(err):
// ignore
default:
return nil, err
}
// lookup type in extended attributes
attr, err = n.SpaceRoot.XattrString(ctx, prefixes.OwnerTypeAttr)
switch {
@@ -160,6 +160,9 @@ func NewDefault(m map[string]interface{}, bs tree.Blobstore, es events.Stream, l
microstore.Table(o.IDCache.Table),
store.DisablePersistence(o.IDCache.DisablePersistence),
store.Authentication(o.IDCache.AuthUsername, o.IDCache.AuthPassword),
store.TLSEnabled(o.IDCache.TLSEnabled),
store.TLSInsecure(o.IDCache.TLSInsecure),
store.TLSRootCA(o.IDCache.TLSRootCACertificate),
), log)
permissionsSelector, err := pool.PermissionsSelector(o.PermissionsSVC, pool.WithTLSMode(o.PermTLSMode))
+39
View File
@@ -103,3 +103,42 @@ func Authentication(username, password string) store.Option {
o.Context = context.WithValue(o.Context, authenticationContextKey{}, []string{username, password})
}
}
type tlsEnabledContextKey struct{}
// TLSEnabled configures whether to use TLS or not. Only supported by the `natsjs` and `natsjskv` implementations.
func TLSEnabled(enabled bool) store.Option {
return func(o *store.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, tlsEnabledContextKey{}, enabled)
}
}
type tlsInsecureContextKey struct{}
// TLSInsecure configures whether to skip TLS certificate verification. Only supported by the `natsjs` and `natsjskv` implementations.
func TLSInsecure(insecure bool) store.Option {
return func(o *store.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, tlsInsecureContextKey{}, insecure)
}
}
type tlsRootCAContextKey struct{}
// TLSRootCA configures the root CA certificate to use for TLS verification. Only supported by the `natsjs` and `natsjskv` implementations.
func TLSRootCA(rootCA string) store.Option {
return func(o *store.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, tlsRootCAContextKey{}, rootCA)
}
}
+43 -33
View File
@@ -20,6 +20,7 @@ package store
import (
"context"
"crypto/tls"
"strings"
"time"
@@ -64,6 +65,11 @@ func Create(opts ...microstore.Option) microstore.Store {
o(options)
}
// ensure we have a logger
if options.Logger == nil {
options.Logger = logger.DefaultLogger
}
storeType, _ := options.Context.Value(typeContextKey{}).(string)
switch storeType {
@@ -118,51 +124,55 @@ func Create(opts ...microstore.Option) microstore.Store {
}
return *ocMemStore
case TypeNatsJS:
ttl, _ := options.Context.Value(ttlContextKey{}).(time.Duration)
if mem, _ := options.Context.Value(disablePersistanceContextKey{}).(bool); mem {
opts = append(opts, natsjs.DefaultMemory())
}
// TODO nats needs a DefaultTTL option as it does not support per Write TTL ...
// FIXME nats has restrictions on the key, we cannot use slashes AFAICT
// host, port, clusterid
natsOptions := nats.GetDefaultOptions()
natsOptions.Name = "TODO" // we can pass in the service name to allow identifying the client, but that requires adding a custom context option
if auth, ok := options.Context.Value(authenticationContextKey{}).([]string); ok && len(auth) == 2 {
natsOptions.User = auth[0]
natsOptions.Password = auth[1]
}
opts, ttl, natsOptions := natsConfig(options.Logger, options.Context, opts)
return natsjs.NewStore(
append(opts,
natsjs.NatsOptions(natsOptions), // always pass in properly initialized default nats options
natsjs.DefaultTTL(ttl))...,
) // TODO test with OpenCloud nats
natsjs.DefaultTTL(ttl))..., // nats needs a DefaultTTL option as it does not support per Write TTL
)
case TypeNatsJSKV:
// NOTE: nats needs a DefaultTTL option as it does not support per Write TTL ...
ttl, _ := options.Context.Value(ttlContextKey{}).(time.Duration)
if mem, _ := options.Context.Value(disablePersistanceContextKey{}).(bool); mem {
opts = append(opts, natsjskv.DefaultMemory())
}
natsOptions := nats.GetDefaultOptions()
natsOptions.Name = "TODO" // we can pass in the service name to allow identifying the client, but that requires adding a custom context option
if auth, ok := options.Context.Value(authenticationContextKey{}).([]string); ok && len(auth) == 2 {
natsOptions.User = auth[0]
natsOptions.Password = auth[1]
}
opts, ttl, natsOptions := natsConfig(options.Logger, options.Context, opts)
return natsjskv.NewStore(
append(opts,
natsjskv.NatsOptions(natsOptions), // always pass in properly initialized default nats options
natsjskv.EncodeKeys(),
natsjskv.DefaultTTL(ttl))...,
natsjskv.EncodeKeys(), // nats has restrictions on the key, we cannot use slashes
natsjskv.DefaultTTL(ttl))..., // nats needs a DefaultTTL option as it does not support per Write TTL
)
case TypeMemory, "mem", "": // allow existing short form and use as default
return microstore.NewMemoryStore(opts...)
default:
// try to log an error
if options.Logger == nil {
options.Logger = logger.DefaultLogger
}
options.Logger.Logf(logger.ErrorLevel, "unknown store type: '%s', falling back to memory", storeType)
return microstore.NewMemoryStore(opts...)
}
}
func natsConfig(log logger.Logger, ctx context.Context, opts []microstore.Option) ([]microstore.Option, time.Duration, nats.Options) {
if mem, _ := ctx.Value(disablePersistanceContextKey{}).(bool); mem {
opts = append(opts, natsjs.DefaultMemory())
}
ttl, _ := ctx.Value(ttlContextKey{}).(time.Duration)
// preparing natsOptions before the switch to reuse the same code
natsOptions := nats.GetDefaultOptions()
natsOptions.Name = "TODO" // we can pass in the service name to allow identifying the client, but that requires adding a custom context option
if auth, ok := ctx.Value(authenticationContextKey{}).([]string); ok && len(auth) == 2 {
natsOptions.User = auth[0]
natsOptions.Password = auth[1]
}
if enableTLS, ok := ctx.Value(tlsEnabledContextKey{}).(bool); ok && enableTLS {
if rootca, ok := ctx.Value(tlsRootCAContextKey{}).(string); ok && rootca != "" {
// when root ca is configured use it. an insecure flag is ignored.
if err := nats.RootCAs(rootca)(&natsOptions); err != nil {
log.Log(logger.ErrorLevel, err)
}
} else {
// enable tls with insecure option
insecure := ctx.Value(tlsInsecureContextKey{}).(bool)
_ = nats.Secure(&tls.Config{MinVersion: tls.VersionTLS12, InsecureSkipVerify: insecure})(&natsOptions)
}
}
return opts, ttl, natsOptions
}
-1
View File
@@ -12,7 +12,6 @@
// limitations under the License.
//go:build dev
// +build dev
package asset
File diff suppressed because one or more lines are too long
@@ -113,7 +113,7 @@ func NewFlags(logger *slog.Logger, features string) (Flagger, error) {
return NoopFlags{}, nil
}
for _, feature := range strings.Split(features, ",") {
for feature := range strings.SplitSeq(features, ",") {
switch feature {
case FeatureReceiverNameInMetrics:
opts = append(opts, enableReceiverNameInMetrics())
+2 -6
View File
@@ -16,6 +16,7 @@ package parse
import (
"errors"
"fmt"
"slices"
"strconv"
"unicode/utf8"
)
@@ -73,12 +74,7 @@ func (t token) isEOF() bool {
// isOneOf returns true if the token is one of the specified kinds.
func (t token) isOneOf(kinds ...tokenKind) bool {
for _, k := range kinds {
if k == t.kind {
return true
}
}
return false
return slices.Contains(kinds, t.kind)
}
// unquote the value in token. If unquoted returns it unmodified.
+2 -2
View File
@@ -127,8 +127,8 @@ func ParseMatcher(s string) (_ *Matcher, err error) {
expectTrailingQuote bool
)
if strings.HasPrefix(rawValue, "\"") {
rawValue = strings.TrimPrefix(rawValue, "\"")
if after, ok := strings.CutPrefix(rawValue, "\""); ok {
rawValue = after
expectTrailingQuote = true
}
+11
View File
@@ -217,3 +217,14 @@ Alerts Resolved:
{{ define "rocketchat.default.emoji" }}{{ end }}
{{ define "rocketchat.default.iconurl" }}{{ end }}
{{ define "rocketchat.default.text" }}{{ end }}
{{ define "mattermost.default.text" }}
{{ if gt (len .Alerts.Firing) 0 }}
# Alerts Firing:
{{ template "__text_alert_list_markdown" .Alerts.Firing }}
{{ end }}
{{ if gt (len .Alerts.Resolved) 0 }}
# Alerts Resolved:
{{ template "__text_alert_list_markdown" .Alerts.Resolved }}
{{ end }}
{{ end }}
+77
View File
@@ -15,11 +15,13 @@ package template
import (
"bytes"
"encoding/json"
tmplhtml "html/template"
"io"
"net/url"
"path"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
@@ -30,6 +32,7 @@ import (
"github.com/prometheus/common/model"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"gopkg.in/yaml.v2"
"github.com/prometheus/alertmanager/asset"
"github.com/prometheus/alertmanager/types"
@@ -186,6 +189,10 @@ var DefaultFuncs = FuncMap{
"safeHtml": func(text string) tmplhtml.HTML {
return tmplhtml.HTML(text)
},
"safeUrl": func(text string) tmplhtml.URL {
return tmplhtml.URL(text)
},
"urlUnescape": url.QueryUnescape,
"reReplaceAll": func(pattern, repl, text string) string {
re := regexp.MustCompile(pattern)
return re.ReplaceAllString(text, repl)
@@ -207,6 +214,13 @@ var DefaultFuncs = FuncMap{
},
"since": time.Since,
"humanizeDuration": commonTemplates.HumanizeDuration,
"toJson": func(v any) (string, error) {
bytes, err := json.Marshal(v)
if err != nil {
return "", err
}
return string(bytes), nil
},
}
// Pair is a key/value string pair.
@@ -423,3 +437,66 @@ func (t *Template) Data(recv string, groupLabels model.LabelSet, alerts ...*type
return data
}
type TemplateFunc func(string) (string, error)
// DeepCopyWithTemplate returns a deep copy of a map/slice/array/string/int/bool or combination thereof, executing the
// provided template (with the provided data) on all string keys or values. All maps are connverted to
// map[string]any, with all non-string keys discarded.
func DeepCopyWithTemplate(value any, tmplTextFunc TemplateFunc) (any, error) {
if value == nil {
return value, nil
}
valueMeta := reflect.ValueOf(value)
switch valueMeta.Kind() {
case reflect.String:
parsed, ok := tmplTextFunc(value.(string))
if ok == nil {
var inlineType any
err := yaml.Unmarshal([]byte(parsed), &inlineType)
if err != nil || (inlineType != nil && reflect.TypeOf(inlineType).Kind() == reflect.String) {
// ignore error, thus the string is not an interface
return parsed, ok
}
return DeepCopyWithTemplate(inlineType, tmplTextFunc)
}
return parsed, ok
case reflect.Array, reflect.Slice:
arrayLen := valueMeta.Len()
converted := make([]any, arrayLen)
for i := range arrayLen {
var err error
converted[i], err = DeepCopyWithTemplate(valueMeta.Index(i).Interface(), tmplTextFunc)
if err != nil {
return nil, err
}
}
return converted, nil
case reflect.Map:
keys := valueMeta.MapKeys()
converted := make(map[string]any, len(keys))
for _, keyMeta := range keys {
var err error
strKey, isString := keyMeta.Interface().(string)
if !isString {
continue
}
strKey, err = tmplTextFunc(strKey)
if err != nil {
return nil, err
}
converted[strKey], err = DeepCopyWithTemplate(valueMeta.MapIndex(keyMeta).Interface(), tmplTextFunc)
if err != nil {
return nil, err
}
}
return converted, nil
default:
return value, nil
}
}
+4 -3
View File
@@ -14,6 +14,7 @@
package types
import (
"context"
"fmt"
"strings"
"sync"
@@ -471,7 +472,7 @@ func (a *Alert) Merge(o *Alert) *Alert {
// maintain an underlying AlertMarker are expected to update it during a call of
// Mutes.
type Muter interface {
Mutes(model.LabelSet) bool
Mutes(ctx context.Context, lset model.LabelSet) bool
}
// A TimeMuter determines if the time is muted by one or more active or mute
@@ -482,10 +483,10 @@ type TimeMuter interface {
}
// A MuteFunc is a function that implements the Muter interface.
type MuteFunc func(model.LabelSet) bool
type MuteFunc func(ctx context.Context, lset model.LabelSet) bool
// Mutes implements the Muter interface.
func (f MuteFunc) Mutes(lset model.LabelSet) bool { return f(lset) }
func (f MuteFunc) Mutes(ctx context.Context, lset model.LabelSet) bool { return f(ctx, lset) }
// A Silence determines whether a given label set is muted.
type Silence struct {
+20 -20
View File
@@ -160,38 +160,38 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
n, err = w.WriteString("# HELP ")
written += n
if err != nil {
return
return written, err
}
n, err = writeName(w, compliantName)
written += n
if err != nil {
return
return written, err
}
err = w.WriteByte(' ')
written++
if err != nil {
return
return written, err
}
n, err = writeEscapedString(w, *in.Help, true)
written += n
if err != nil {
return
return written, err
}
err = w.WriteByte('\n')
written++
if err != nil {
return
return written, err
}
}
n, err = w.WriteString("# TYPE ")
written += n
if err != nil {
return
return written, err
}
n, err = writeName(w, compliantName)
written += n
if err != nil {
return
return written, err
}
switch metricType {
case dto.MetricType_COUNTER:
@@ -215,34 +215,34 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
}
written += n
if err != nil {
return
return written, err
}
if toOM.withUnit && in.Unit != nil {
n, err = w.WriteString("# UNIT ")
written += n
if err != nil {
return
return written, err
}
n, err = writeName(w, compliantName)
written += n
if err != nil {
return
return written, err
}
err = w.WriteByte(' ')
written++
if err != nil {
return
return written, err
}
n, err = writeEscapedString(w, *in.Unit, true)
written += n
if err != nil {
return
return written, err
}
err = w.WriteByte('\n')
written++
if err != nil {
return
return written, err
}
}
@@ -306,7 +306,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
)
written += n
if err != nil {
return
return written, err
}
}
n, err = writeOpenMetricsSample(
@@ -316,7 +316,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
)
written += n
if err != nil {
return
return written, err
}
n, err = writeOpenMetricsSample(
w, compliantName, "_count", metric, "", 0,
@@ -349,7 +349,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
)
written += n
if err != nil {
return
return written, err
}
if math.IsInf(b.GetUpperBound(), +1) {
infSeen = true
@@ -367,7 +367,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
// out if needed).
written += n
if err != nil {
return
return written, err
}
}
n, err = writeOpenMetricsSample(
@@ -377,7 +377,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
)
written += n
if err != nil {
return
return written, err
}
if metric.Histogram.GetSampleCountFloat() > 0 {
return written, fmt.Errorf(
@@ -401,10 +401,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E
}
written += n
if err != nil {
return
return written, err
}
}
return
return written, err
}
// FinalizeOpenMetrics writes the final `# EOF\n` line required by OpenMetrics.
+15 -15
View File
@@ -108,38 +108,38 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
n, err = w.WriteString("# HELP ")
written += n
if err != nil {
return
return written, err
}
n, err = writeName(w, name)
written += n
if err != nil {
return
return written, err
}
err = w.WriteByte(' ')
written++
if err != nil {
return
return written, err
}
n, err = writeEscapedString(w, *in.Help, false)
written += n
if err != nil {
return
return written, err
}
err = w.WriteByte('\n')
written++
if err != nil {
return
return written, err
}
}
n, err = w.WriteString("# TYPE ")
written += n
if err != nil {
return
return written, err
}
n, err = writeName(w, name)
written += n
if err != nil {
return
return written, err
}
metricType := in.GetType()
switch metricType {
@@ -161,7 +161,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
}
written += n
if err != nil {
return
return written, err
}
// Finally the samples, one line for each.
@@ -211,7 +211,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
)
written += n
if err != nil {
return
return written, err
}
}
n, err = writeSample(
@@ -220,7 +220,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
)
written += n
if err != nil {
return
return written, err
}
n, err = writeSample(
w, name, "_count", metric, "", 0,
@@ -245,7 +245,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
)
written += n
if err != nil {
return
return written, err
}
if math.IsInf(b.GetUpperBound(), +1) {
infSeen = true
@@ -263,7 +263,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
)
written += n
if err != nil {
return
return written, err
}
}
n, err = writeSample(
@@ -272,7 +272,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
)
written += n
if err != nil {
return
return written, err
}
v := metric.Histogram.GetSampleCountFloat()
if v == 0 {
@@ -286,10 +286,10 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
}
written += n
if err != nil {
return
return written, err
}
}
return
return written, err
}
// writeSample writes a single sample in text format to w, given the metric
+1 -1
View File
@@ -26,7 +26,7 @@ import (
var (
// MinClusterVersion is the min cluster version this etcd binary is compatible with.
MinClusterVersion = "3.0.0"
Version = "3.6.6"
Version = "3.6.7"
APIVersion = "unknown"
// Git SHA Value will be set during build
@@ -52,6 +52,12 @@ type Option interface {
apply(*config)
}
type optionFunc func(*config)
func (f optionFunc) apply(c *config) {
f(c)
}
// newConfig returns a config configured with all the passed Options.
func newConfig(opts []Option) *config {
c := &config{
@@ -65,27 +71,13 @@ func newConfig(opts []Option) *config {
return c
}
type publicEndpointOption struct{ p bool }
func (o publicEndpointOption) apply(c *config) {
c.PublicEndpoint = o.p
}
// WithPublicEndpoint configures the Handler to link the span with an incoming
// span context. If this option is not provided, then the association is a child
// association instead of a link.
func WithPublicEndpoint() Option {
return publicEndpointOption{p: true}
}
type publicEndpointFnOption struct {
fn func(context.Context, *stats.RPCTagInfo) bool
}
func (o publicEndpointFnOption) apply(c *config) {
if o.fn != nil {
c.PublicEndpointFn = o.fn
}
return optionFunc(func(c *config) {
c.PublicEndpoint = true
})
}
// WithPublicEndpointFn runs with every request, and allows conditionally
@@ -94,81 +86,55 @@ func (o publicEndpointFnOption) apply(c *config) {
// child association instead of a link.
// Note: WithPublicEndpoint takes precedence over WithPublicEndpointFn.
func WithPublicEndpointFn(fn func(context.Context, *stats.RPCTagInfo) bool) Option {
return publicEndpointFnOption{fn: fn}
}
type propagatorsOption struct{ p propagation.TextMapPropagator }
func (o propagatorsOption) apply(c *config) {
if o.p != nil {
c.Propagators = o.p
}
return optionFunc(func(c *config) {
c.PublicEndpointFn = fn
})
}
// WithPropagators returns an Option to use the Propagators when extracting
// and injecting trace context from requests.
func WithPropagators(p propagation.TextMapPropagator) Option {
return propagatorsOption{p: p}
}
type tracerProviderOption struct{ tp trace.TracerProvider }
func (o tracerProviderOption) apply(c *config) {
if o.tp != nil {
c.TracerProvider = o.tp
}
return optionFunc(func(c *config) {
if p != nil {
c.Propagators = p
}
})
}
// WithInterceptorFilter returns an Option to use the request filter.
//
// Deprecated: Use stats handlers instead.
func WithInterceptorFilter(f InterceptorFilter) Option {
return interceptorFilterOption{f: f}
}
type interceptorFilterOption struct {
f InterceptorFilter
}
func (o interceptorFilterOption) apply(c *config) {
if o.f != nil {
c.InterceptorFilter = o.f
}
return optionFunc(func(c *config) {
if f != nil {
c.InterceptorFilter = f
}
})
}
// WithFilter returns an Option to use the request filter.
func WithFilter(f Filter) Option {
return filterOption{f: f}
}
type filterOption struct {
f Filter
}
func (o filterOption) apply(c *config) {
if o.f != nil {
c.Filter = o.f
}
return optionFunc(func(c *config) {
if f != nil {
c.Filter = f
}
})
}
// WithTracerProvider returns an Option to use the TracerProvider when
// creating a Tracer.
func WithTracerProvider(tp trace.TracerProvider) Option {
return tracerProviderOption{tp: tp}
}
type meterProviderOption struct{ mp metric.MeterProvider }
func (o meterProviderOption) apply(c *config) {
if o.mp != nil {
c.MeterProvider = o.mp
}
return optionFunc(func(c *config) {
c.TracerProvider = tp
})
}
// WithMeterProvider returns an Option to use the MeterProvider when
// creating a Meter. If this option is not provide the global MeterProvider will be used.
func WithMeterProvider(mp metric.MeterProvider) Option {
return meterProviderOption{mp: mp}
return optionFunc(func(c *config) {
c.MeterProvider = mp
})
}
// Event type that can be recorded, see WithMessageEvents.
@@ -180,21 +146,6 @@ const (
SentEvents
)
type messageEventsProviderOption struct {
events []Event
}
func (m messageEventsProviderOption) apply(c *config) {
for _, e := range m.events {
switch e {
case ReceivedEvents:
c.ReceivedEvent = true
case SentEvents:
c.SentEvent = true
}
}
}
// WithMessageEvents configures the Handler to record the specified events
// (span.AddEvent) on spans. By default only summary attributes are added at the
// end of the request.
@@ -203,13 +154,16 @@ func (m messageEventsProviderOption) apply(c *config) {
// - ReceivedEvents: Record the number of bytes read after every gRPC read operation.
// - SentEvents: Record the number of bytes written after every gRPC write operation.
func WithMessageEvents(events ...Event) Option {
return messageEventsProviderOption{events: events}
}
type spanStartOption struct{ opts []trace.SpanStartOption }
func (o spanStartOption) apply(c *config) {
c.SpanStartOptions = append(c.SpanStartOptions, o.opts...)
return optionFunc(func(c *config) {
for _, e := range events {
switch e {
case ReceivedEvents:
c.ReceivedEvent = true
case SentEvents:
c.SentEvent = true
}
}
})
}
// WithSpanOptions configures an additional set of
@@ -217,31 +171,25 @@ func (o spanStartOption) apply(c *config) {
//
// Deprecated: It is only used by the deprecated interceptor, and is unused by [NewClientHandler] and [NewServerHandler].
func WithSpanOptions(opts ...trace.SpanStartOption) Option {
return spanStartOption{opts}
}
type spanAttributesOption struct{ a []attribute.KeyValue }
func (o spanAttributesOption) apply(c *config) {
if o.a != nil {
c.SpanAttributes = o.a
}
return optionFunc(func(c *config) {
c.SpanStartOptions = append(c.SpanStartOptions, opts...)
})
}
// WithSpanAttributes returns an Option to add custom attributes to the spans.
func WithSpanAttributes(a ...attribute.KeyValue) Option {
return spanAttributesOption{a: a}
}
type metricAttributesOption struct{ a []attribute.KeyValue }
func (o metricAttributesOption) apply(c *config) {
if o.a != nil {
c.MetricAttributes = o.a
}
return optionFunc(func(c *config) {
if a != nil {
c.SpanAttributes = a
}
})
}
// WithMetricAttributes returns an Option to add custom attributes to the metrics.
func WithMetricAttributes(a ...attribute.KeyValue) Option {
return metricAttributesOption{a: a}
return optionFunc(func(c *config) {
if a != nil {
c.MetricAttributes = append(c.MetricAttributes, a...)
}
})
}
@@ -6,9 +6,7 @@ package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.g
import (
"context"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc/metadata"
)
@@ -17,9 +15,9 @@ type metadataSupplier struct {
}
// assert that metadataSupplier implements the TextMapCarrier interface.
var _ propagation.TextMapCarrier = &metadataSupplier{}
var _ propagation.TextMapCarrier = metadataSupplier{}
func (s *metadataSupplier) Get(key string) string {
func (s metadataSupplier) Get(key string) string {
values := s.metadata.Get(key)
if len(values) == 0 {
return ""
@@ -27,11 +25,11 @@ func (s *metadataSupplier) Get(key string) string {
return values[0]
}
func (s *metadataSupplier) Set(key, value string) {
func (s metadataSupplier) Set(key, value string) {
s.metadata.Set(key, value)
}
func (s *metadataSupplier) Keys() []string {
func (s metadataSupplier) Keys() []string {
out := make([]string, 0, len(s.metadata))
for key := range s.metadata {
out = append(out, key)
@@ -39,50 +37,24 @@ func (s *metadataSupplier) Keys() []string {
return out
}
// Inject injects correlation context and span context into the gRPC
// metadata object. This function is meant to be used on outgoing
// requests.
//
// Deprecated: Unnecessary public func.
func Inject(ctx context.Context, md *metadata.MD, opts ...Option) {
c := newConfig(opts)
c.Propagators.Inject(ctx, &metadataSupplier{
metadata: *md,
})
}
func inject(ctx context.Context, propagators propagation.TextMapPropagator) context.Context {
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.MD{}
}
propagators.Inject(ctx, &metadataSupplier{
propagators.Inject(ctx, metadataSupplier{
metadata: md,
})
return metadata.NewOutgoingContext(ctx, md)
}
// Extract returns the correlation context and span context that
// another service encoded in the gRPC metadata object with Inject.
// This function is meant to be used on incoming requests.
//
// Deprecated: Unnecessary public func.
func Extract(ctx context.Context, md *metadata.MD, opts ...Option) (baggage.Baggage, trace.SpanContext) {
c := newConfig(opts)
ctx = c.Propagators.Extract(ctx, &metadataSupplier{
metadata: *md,
})
return baggage.FromContext(ctx), trace.SpanContextFromContext(ctx)
}
func extract(ctx context.Context, propagators propagation.TextMapPropagator) context.Context {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
md = metadata.MD{}
}
return propagators.Extract(ctx, &metadataSupplier{
return propagators.Extract(ctx, metadataSupplier{
metadata: md,
})
}
@@ -26,10 +26,11 @@ import (
type gRPCContextKey struct{}
type gRPCContext struct {
inMessages int64
outMessages int64
metricAttrs []attribute.KeyValue
record bool
inMessages int64
outMessages int64
metricAttrs []attribute.KeyValue
metricAttrSet attribute.Set
record bool
}
type serverHandler struct {
@@ -38,8 +39,8 @@ type serverHandler struct {
tracer trace.Tracer
duration rpcconv.ServerDuration
inSize rpcconv.ServerRequestSize
outSize rpcconv.ServerResponseSize
inSize int64Hist
outSize int64Hist
inMsg rpcconv.ServerRequestsPerRPC
outMsg rpcconv.ServerResponsesPerRPC
}
@@ -111,9 +112,12 @@ func (h *serverHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
}
if record {
// Make a new slice to avoid aliasing into the same attrs slice used by metrics.
spanAttributes := make([]attribute.KeyValue, 0, len(attrs)+len(h.SpanAttributes))
spanAttributes = append(append(spanAttributes, attrs...), h.SpanAttributes...)
opts := []trace.SpanStartOption{
trace.WithSpanKind(trace.SpanKindServer),
trace.WithAttributes(append(attrs, h.SpanAttributes...)...),
trace.WithAttributes(spanAttributes...),
}
if h.PublicEndpoint || (h.PublicEndpointFn != nil && h.PublicEndpointFn(ctx, info)) {
opts = append(opts, trace.WithNewRoot())
@@ -133,6 +137,7 @@ func (h *serverHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
metricAttrs: append(attrs, h.MetricAttributes...),
record: record,
}
gctx.metricAttrSet = attribute.NewSet(gctx.metricAttrs...)
return context.WithValue(ctx, gRPCContextKey{}, &gctx)
}
@@ -157,8 +162,8 @@ type clientHandler struct {
tracer trace.Tracer
duration rpcconv.ClientDuration
inSize rpcconv.ClientResponseSize
outSize rpcconv.ClientRequestSize
inSize int64Hist
outSize int64Hist
inMsg rpcconv.ClientResponsesPerRPC
outMsg rpcconv.ClientRequestsPerRPC
}
@@ -219,11 +224,14 @@ func (h *clientHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
}
if record {
// Make a new slice to avoid aliasing into the same attrs slice used by metrics.
spanAttributes := make([]attribute.KeyValue, 0, len(attrs)+len(h.SpanAttributes))
spanAttributes = append(append(spanAttributes, attrs...), h.SpanAttributes...)
ctx, _ = h.tracer.Start(
ctx,
name,
trace.WithSpanKind(trace.SpanKindClient),
trace.WithAttributes(append(attrs, h.SpanAttributes...)...),
trace.WithAttributes(spanAttributes...),
)
}
@@ -231,6 +239,7 @@ func (h *clientHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
metricAttrs: append(attrs, h.MetricAttributes...),
record: record,
}
gctx.metricAttrSet = attribute.NewSet(gctx.metricAttrs...)
return inject(context.WithValue(ctx, gRPCContextKey{}, &gctx), h.Propagators)
}
@@ -262,7 +271,7 @@ func (*clientHandler) HandleConn(context.Context, stats.ConnStats) {
}
type int64Hist interface {
Record(context.Context, int64, ...attribute.KeyValue)
RecordSet(context.Context, int64, attribute.Set)
}
func (c *config) handleRPC(
@@ -286,7 +295,7 @@ func (c *config) handleRPC(
case *stats.InPayload:
if gctx != nil {
messageId = atomic.AddInt64(&gctx.inMessages, 1)
inSize.Record(ctx, int64(rs.Length), gctx.metricAttrs...)
inSize.RecordSet(ctx, int64(rs.Length), gctx.metricAttrSet)
}
if c.ReceivedEvent && span.IsRecording() {
@@ -302,7 +311,7 @@ func (c *config) handleRPC(
case *stats.OutPayload:
if gctx != nil {
messageId = atomic.AddInt64(&gctx.outMessages, 1)
outSize.Record(ctx, int64(rs.Length), gctx.metricAttrs...)
outSize.RecordSet(ctx, int64(rs.Length), gctx.metricAttrSet)
}
if c.SentEvent && span.IsRecording() {
@@ -343,6 +352,9 @@ func (c *config) handleRPC(
var metricAttrs []attribute.KeyValue
if gctx != nil {
// Don't use gctx.metricAttrSet here, because it requires passing
// multiple RecordOptions, which would call metric.mergeSets and
// allocate a new set for each Record call.
metricAttrs = make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1)
metricAttrs = append(metricAttrs, gctx.metricAttrs...)
}
@@ -5,6 +5,6 @@ package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.g
// Version is the current release version of the gRPC instrumentation.
func Version() string {
return "0.63.0"
return "0.64.0"
// This string is updated by the pre_release.sh script during release
}
@@ -113,7 +113,7 @@ func span(sd tracesdk.ReadOnlySpan) *tracepb.Span {
if psid := sd.Parent().SpanID(); psid.IsValid() {
s.ParentSpanId = psid[:]
}
s.Flags = buildSpanFlags(sd.Parent())
s.Flags = buildSpanFlagsWith(sd.SpanContext().TraceFlags(), sd.Parent())
return s
}
@@ -159,7 +159,7 @@ func links(links []tracesdk.Link) []*tracepb.Span_Link {
tid := otLink.SpanContext.TraceID()
sid := otLink.SpanContext.SpanID()
flags := buildSpanFlags(otLink.SpanContext)
flags := buildSpanFlagsWith(otLink.SpanContext.TraceFlags(), otLink.SpanContext)
sl = append(sl, &tracepb.Span_Link{
TraceId: tid[:],
@@ -172,13 +172,15 @@ func links(links []tracesdk.Link) []*tracepb.Span_Link {
return sl
}
func buildSpanFlags(sc trace.SpanContext) uint32 {
flags := tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
if sc.IsRemote() {
flags |= tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
func buildSpanFlagsWith(tf trace.TraceFlags, parent trace.SpanContext) uint32 {
// Lower 8 bits are the W3C TraceFlags; always indicate that we know whether the parent is remote
flags := uint32(tf) | uint32(tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK)
// Set the parent-is-remote bit when applicable
if parent.IsRemote() {
flags |= uint32(tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK)
}
return uint32(flags) // nolint:gosec // Flags is a bitmask and can't be negative
return flags // nolint:gosec // Flags is a bitmask and can't be negative
}
// spanEvents transforms span Events to an OTLP span events.
@@ -17,9 +17,10 @@ import (
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/counter"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry"
)
@@ -45,6 +46,9 @@ type client struct {
conn *grpc.ClientConn
tscMu sync.RWMutex
tsc coltracepb.TraceServiceClient
instID int64
inst *observ.Instrumentation
}
// Compile time check *client implements otlptrace.Client.
@@ -68,6 +72,7 @@ func newClient(opts ...Option) *client {
stopCtx: ctx,
stopFunc: cancel,
conn: cfg.GRPCConn,
instID: counter.NextExporterID(),
}
if len(cfg.Traces.Headers) > 0 {
@@ -92,13 +97,24 @@ func (c *client) Start(context.Context) error {
c.conn = conn
}
// Initialize the instrumentation if not already done.
//
// Initialize here instead of NewClient to allow any errors to be passed
// back to the caller and so that any setup of the environment variables to
// enable instrumentation can be set via code.
var err error
if c.inst == nil {
target := c.conn.CanonicalTarget()
c.inst, err = observ.NewInstrumentation(c.instID, target)
}
// The otlptrace.Client interface states this method is called just once,
// so no need to check if already started.
c.tscMu.Lock()
c.tsc = coltracepb.NewTraceServiceClient(c.conn)
c.tscMu.Unlock()
return nil
return err
}
var errAlreadyStopped = errors.New("the client is already stopped")
@@ -174,7 +190,7 @@ var errShutdown = errors.New("the client is shutdown")
//
// Retryable errors from the server will be handled according to any
// RetryConfig the client was created with.
func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.ResourceSpans) error {
func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.ResourceSpans) (uploadErr error) {
// Hold a read lock to ensure a shut down initiated after this starts does
// not abandon the export. This read lock acquire has less priority than a
// write lock acquire (i.e. Stop), meaning if the client is shutting down
@@ -189,6 +205,12 @@ func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.Resourc
ctx, cancel := c.exportContext(ctx)
defer cancel()
var code codes.Code
if c.inst != nil {
op := c.inst.ExportSpans(ctx, len(protoSpans))
defer func() { op.End(uploadErr, code) }()
}
return c.requestFunc(ctx, func(iCtx context.Context) error {
resp, err := c.tsc.Export(iCtx, &coltracepb.ExportTraceServiceRequest{
ResourceSpans: protoSpans,
@@ -197,16 +219,17 @@ func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.Resourc
msg := resp.PartialSuccess.GetErrorMessage()
n := resp.PartialSuccess.GetRejectedSpans()
if n != 0 || msg != "" {
err := internal.TracePartialSuccessError(n, msg)
otel.Handle(err)
e := internal.TracePartialSuccessError(n, msg)
uploadErr = errors.Join(uploadErr, e)
}
}
// nil is converted to OK.
if status.Code(err) == codes.OK {
code = status.Code(err)
if code == codes.OK {
// Success.
return nil
return uploadErr
}
return err
return errors.Join(uploadErr, err)
})
}
@@ -0,0 +1,31 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/counter/counter.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package counter provides a simple counter for generating unique IDs.
//
// This package is used to generate unique IDs while allowing testing packages
// to reset the counter.
package counter // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/counter"
import "sync/atomic"
// exporterN is a global 0-based count of the number of exporters created.
var exporterN atomic.Int64
// NextExporterID returns the next unique ID for an exporter.
func NextExporterID() int64 {
const inc = 1
return exporterN.Add(inc) - inc
}
// SetExporterID sets the exporter ID counter to v and returns the previous
// value.
//
// This function is useful for testing purposes, allowing you to reset the
// counter. It should not be used in production code.
func SetExporterID(v int64) int64 {
return exporterN.Swap(v)
}
@@ -23,3 +23,12 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/ot
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlptracetest/collector.go.tmpl "--data={}" --out=otlptracetest/collector.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlptracetest/data.go.tmpl "--data={}" --out=otlptracetest/data.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlptracetest/otlptest.go.tmpl "--data={}" --out=otlptracetest/otlptest.go
//go:generate gotmpl --body=../../../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc\" }" --out=x/x.go
//go:generate gotmpl --body=../../../../../internal/shared/x/x_test.go.tmpl "--data={}" --out=x/x_test.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/observ/target.go.tmpl "--data={ \"pkg\": \"observ\", \"pkg_path\": \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ\" }" --out=observ/target.go
//go:generate gotmpl --body=../../../../../internal/shared/otlp/observ/target_test.go.tmpl "--data={ \"pkg\": \"observ\" }" --out=observ/target_test.go
//go:generate gotmpl --body=../../../../../internal/shared/counter/counter.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/counter\" }" --out=counter/counter.go
//go:generate gotmpl --body=../../../../../internal/shared/counter/counter_test.go.tmpl "--data={}" --out=counter/counter_test.go
@@ -0,0 +1,6 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package observ provides experimental observability instrumentation for the
// otlptracegrpc exporter.
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"
@@ -0,0 +1,341 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"
import (
"context"
"errors"
"fmt"
"sync"
"time"
"google.golang.org/grpc/codes"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x"
"go.opentelemetry.io/otel/internal/global"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
)
const (
// ScopeName is the unique name of the meter used for instrumentation.
ScopeName = "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"
// SchemaURL is the schema URL of the metrics produced by this
// instrumentation.
SchemaURL = semconv.SchemaURL
// Version is the current version of this instrumentation.
//
// This matches the version of the exporter.
Version = internal.Version
)
var (
measureAttrsPool = &sync.Pool{
New: func() any {
const n = 1 + // component.name
1 + // component.type
1 + // server.addr
1 + // server.port
1 + // error.type
1 // rpc.grpc.status_code
s := make([]attribute.KeyValue, 0, n)
// Return a pointer to a slice instead of a slice itself
// to avoid allocations on every call.
return &s
},
}
addOptPool = &sync.Pool{
New: func() any {
const n = 1 // WithAttributeSet
o := make([]metric.AddOption, 0, n)
return &o
},
}
recordOptPool = &sync.Pool{
New: func() any {
const n = 1 // WithAttributeSet
o := make([]metric.RecordOption, 0, n)
return &o
},
}
)
func get[T any](p *sync.Pool) *[]T { return p.Get().(*[]T) }
func put[T any](p *sync.Pool, s *[]T) {
*s = (*s)[:0] // Reset.
p.Put(s)
}
// ComponentName returns the component name for the exporter with the
// provided ID.
func ComponentName(id int64) string {
t := semconv.OTelComponentTypeOtlpGRPCSpanExporter.Value.AsString()
return fmt.Sprintf("%s/%d", t, id)
}
// Instrumentation is experimental instrumentation for the exporter.
type Instrumentation struct {
inflightSpans metric.Int64UpDownCounter
exportedSpans metric.Int64Counter
opDuration metric.Float64Histogram
attrs []attribute.KeyValue
addOpt metric.AddOption
recOpt metric.RecordOption
}
// NewInstrumentation returns instrumentation for an OTLP over gPRC trace
// exporter with the provided ID using the global MeterProvider.
//
// The id should be the unique exporter instance ID. It is used
// to set the "component.name" attribute.
//
// The target is the endpoint the exporter is exporting to.
//
// If the experimental observability is disabled, nil is returned.
func NewInstrumentation(id int64, target string) (*Instrumentation, error) {
if !x.Observability.Enabled() {
return nil, nil
}
attrs := BaseAttrs(id, target)
i := &Instrumentation{
attrs: attrs,
addOpt: metric.WithAttributeSet(attribute.NewSet(attrs...)),
// Do not modify attrs (NewSet sorts in-place), make a new slice.
recOpt: metric.WithAttributeSet(attribute.NewSet(append(
// Default to OK status code.
[]attribute.KeyValue{semconv.RPCGRPCStatusCodeOk},
attrs...,
)...)),
}
mp := otel.GetMeterProvider()
m := mp.Meter(
ScopeName,
metric.WithInstrumentationVersion(Version),
metric.WithSchemaURL(SchemaURL),
)
var err error
inflightSpans, e := otelconv.NewSDKExporterSpanInflight(m)
if e != nil {
e = fmt.Errorf("failed to create span inflight metric: %w", e)
err = errors.Join(err, e)
}
i.inflightSpans = inflightSpans.Inst()
exportedSpans, e := otelconv.NewSDKExporterSpanExported(m)
if e != nil {
e = fmt.Errorf("failed to create span exported metric: %w", e)
err = errors.Join(err, e)
}
i.exportedSpans = exportedSpans.Inst()
opDuration, e := otelconv.NewSDKExporterOperationDuration(m)
if e != nil {
e = fmt.Errorf("failed to create operation duration metric: %w", e)
err = errors.Join(err, e)
}
i.opDuration = opDuration.Inst()
return i, err
}
// BaseAttrs returns the base attributes for the exporter with the provided ID
// and target.
//
// The id should be the unique exporter instance ID. It is used
// to set the "component.name" attribute.
//
// The target is the gRPC target the exporter is exporting to. It is expected
// to be the output of the Client's CanonicalTarget method.
func BaseAttrs(id int64, target string) []attribute.KeyValue {
host, port, err := ParseCanonicalTarget(target)
if err != nil || (host == "" && port < 0) {
if err != nil {
global.Debug("failed to parse target", "target", target, "error", err)
}
return []attribute.KeyValue{
semconv.OTelComponentName(ComponentName(id)),
semconv.OTelComponentTypeOtlpGRPCSpanExporter,
}
}
// Do not use append so the slice is exactly allocated.
if port < 0 {
return []attribute.KeyValue{
semconv.OTelComponentName(ComponentName(id)),
semconv.OTelComponentTypeOtlpGRPCSpanExporter,
semconv.ServerAddress(host),
}
}
if host == "" {
return []attribute.KeyValue{
semconv.OTelComponentName(ComponentName(id)),
semconv.OTelComponentTypeOtlpGRPCSpanExporter,
semconv.ServerPort(port),
}
}
return []attribute.KeyValue{
semconv.OTelComponentName(ComponentName(id)),
semconv.OTelComponentTypeOtlpGRPCSpanExporter,
semconv.ServerAddress(host),
semconv.ServerPort(port),
}
}
// ExportSpans instruments the ExportSpans method of the exporter. It returns
// an [ExportOp] that must have its [ExportOp.End] method called when the
// ExportSpans method returns.
func (i *Instrumentation) ExportSpans(ctx context.Context, nSpans int) ExportOp {
start := time.Now()
addOpt := get[metric.AddOption](addOptPool)
defer put(addOptPool, addOpt)
*addOpt = append(*addOpt, i.addOpt)
i.inflightSpans.Add(ctx, int64(nSpans), *addOpt...)
return ExportOp{
ctx: ctx,
start: start,
nSpans: int64(nSpans),
inst: i,
}
}
// ExportOp tracks the operation being observed by [Instrumentation.ExportSpans].
type ExportOp struct {
ctx context.Context
start time.Time
nSpans int64
inst *Instrumentation
}
// End completes the observation of the operation being observed by a call to
// [Instrumentation.ExportSpans].
//
// Any error that is encountered is provided as err.
//
// If err is not nil, all spans will be recorded as failures unless error is of
// type [internal.PartialSuccess]. In the case of a PartialSuccess, the number
// of successfully exported spans will be determined by inspecting the
// RejectedItems field of the PartialSuccess.
func (e ExportOp) End(err error, code codes.Code) {
addOpt := get[metric.AddOption](addOptPool)
defer put(addOptPool, addOpt)
*addOpt = append(*addOpt, e.inst.addOpt)
e.inst.inflightSpans.Add(e.ctx, -e.nSpans, *addOpt...)
success := successful(e.nSpans, err)
// Record successfully exported spans, even if the value is 0 which are
// meaningful to distribution aggregations.
e.inst.exportedSpans.Add(e.ctx, success, *addOpt...)
if err != nil {
attrs := get[attribute.KeyValue](measureAttrsPool)
defer put(measureAttrsPool, attrs)
*attrs = append(*attrs, e.inst.attrs...)
*attrs = append(*attrs, semconv.ErrorType(err))
// Do not inefficiently make a copy of attrs by using
// WithAttributes instead of WithAttributeSet.
o := metric.WithAttributeSet(attribute.NewSet(*attrs...))
// Reset addOpt with new attribute set.
*addOpt = append((*addOpt)[:0], o)
e.inst.exportedSpans.Add(e.ctx, e.nSpans-success, *addOpt...)
}
recOpt := get[metric.RecordOption](recordOptPool)
defer put(recordOptPool, recOpt)
*recOpt = append(*recOpt, e.inst.recordOption(err, code))
d := time.Since(e.start).Seconds()
e.inst.opDuration.Record(e.ctx, d, *recOpt...)
}
// recordOption returns a RecordOption with attributes representing the
// outcome of the operation being recorded.
//
// If err is nil and code is codes.OK, the default recOpt of the
// Instrumentation is returned.
//
// If err is not nil or code is not codes.OK, a new RecordOption is returned
// with the base attributes of the Instrumentation plus the rpc.grpc.status_code
// attribute set to the provided code, and if err is not nil, the error.type
// attribute set to the type of the error.
func (i *Instrumentation) recordOption(err error, code codes.Code) metric.RecordOption {
if err == nil && code == codes.OK {
return i.recOpt
}
attrs := get[attribute.KeyValue](measureAttrsPool)
defer put(measureAttrsPool, attrs)
*attrs = append(*attrs, i.attrs...)
c := int64(code) // uint32 -> int64.
*attrs = append(*attrs, semconv.RPCGRPCStatusCodeKey.Int64(c))
if err != nil {
*attrs = append(*attrs, semconv.ErrorType(err))
}
// Do not inefficiently make a copy of attrs by using WithAttributes
// instead of WithAttributeSet.
return metric.WithAttributeSet(attribute.NewSet(*attrs...))
}
// successful returns the number of successfully exported spans out of the n
// that were exported based on the provided error.
//
// If err is nil, n is returned. All spans were successfully exported.
//
// If err is not nil and not an [internal.PartialSuccess] error, 0 is returned.
// It is assumed all spans failed to be exported.
//
// If err is an [internal.PartialSuccess] error, the number of successfully
// exported spans is computed by subtracting the RejectedItems field from n. If
// RejectedItems is negative, n is returned. If RejectedItems is greater than
// n, 0 is returned.
func successful(n int64, err error) int64 {
if err == nil {
return n // All spans successfully exported.
}
// Split rejection calculation so successful is inlinable.
return n - rejected(n, err)
}
var errPartialPool = &sync.Pool{
New: func() any { return new(internal.PartialSuccess) },
}
// rejected returns how many out of the n spans exporter were rejected based on
// the provided non-nil err.
func rejected(n int64, err error) int64 {
ps := errPartialPool.Get().(*internal.PartialSuccess)
defer errPartialPool.Put(ps)
// Check for partial success.
if errors.As(err, ps) {
// Bound RejectedItems to [0, n]. This should not be needed,
// but be defensive as this is from an external source.
return min(max(ps.RejectedItems, 0), n)
}
return n // All spans rejected.
}

Some files were not shown because too many files have changed in this diff Show More