{"record":{"id":"216428d6fcae4534","repo":"temporalio/temporal","slug":"errnonpositivetotalnumshards","errorCode":"ErrNonPositiveTotalNumShards","errorMessage":"%w: %d","messagePattern":"%w: %d","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"service/history/shard/ownership_based_quota_scaler.go","lineNumber":66,"sourceCode":"var (\n\t// shardCountNotSet is a sentinel value for the shardCount which indicates that it hasn't been set yet. It's an\n\t// int64 because that's the type of the atomic.\n\tshardCountNotSet int64 = -1\n\n\tErrNonPositiveTotalNumShards = errors.New(\"totalNumShards must be greater than 0\")\n)\n\n// NewOwnershipBasedQuotaScaler returns an OwnershipBasedQuotaScaler. The updateAppliedCallback field is a channel which\n// is sent to in a blocking fashion when the shard count updates are applied. This is useful for testing. In production,\n// you should pass in nil, which will cause the callback to be ignored. If totalNumShards is non-positive, then an error\n// is returned.\nfunc NewOwnershipBasedQuotaScaler(\n\tshardCounter ShardCounter,\n\ttotalNumShards int,\n\tupdateAppliedCallback chan struct{},\n) (*OwnershipBasedQuotaScalerImpl, error) {\n\tif totalNumShards <= 0 {\n\t\treturn nil, fmt.Errorf(\"%w: %d\", ErrNonPositiveTotalNumShards, totalNumShards)\n\t}\n\n\tscaler := &OwnershipBasedQuotaScalerImpl{\n\t\tshardCounter:          shardCounter,\n\t\ttotalNumShards:        totalNumShards,\n\t\tupdateAppliedCallback: updateAppliedCallback,\n\t\tsubscription:          shardCounter.SubscribeShardCount(),\n\t}\n\n\tscaler.shardCount.Store(shardCountNotSet)\n\tscaler.shutdownWG.Go(func() {\n\n\t\tfor count := range scaler.subscription.ShardCount() {\n\t\t\tscaler.shardCount.Store(int64(count))\n\t\t\tif scaler.updateAppliedCallback != nil {\n\t\t\t\tscaler.updateAppliedCallback <- struct{}{}\n\t\t\t}\n\t\t}","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/service/history/shard/ownership_based_quota_scaler.go#L48-L84","documentation":"NewOwnershipBasedQuotaScaler validates that totalNumShards is positive before constructing the scaler. The returned error wraps ErrNonPositiveTotalNumShards with the offending value, guarding against division/mapping errors in rate-limit scaling math driven by the total shard count.","triggerScenarios":"Calling NewOwnershipBasedQuotaScaler with totalNumShards <= 0 — e.g. from an uninitialized shard count source, a config value of 0, or a shardCounter whose total was not yet populated.","commonSituations":"Dynamic config where numHistoryShards is unset/0; a service started before the shard counter was initialized; tests constructing the scaler with a zero default; misconfigured cluster sizing.","solutions":["Pass the cluster's actual numHistoryShards (positive) from config when constructing the scaler","Fix the config/dynamicconfig entry that supplies 0 or a negative shard count","Ensure the ShardCounter is initialized before scaler construction so the total is populated"],"exampleFix":"// before\nscaler, err := NewOwnershipBasedQuotaScaler(counter, cfg.NumShards, cb)\n// after\nif cfg.NumShards <= 0 {\n\treturn fmt.Errorf(\"invalid numHistoryShards: %d\", cfg.NumShards)\n}\nscaler, err := NewOwnershipBasedQuotaScaler(counter, cfg.NumShards, cb)","handlingStrategy":"validation","validationCode":"if totalNumShards <= 0 {\n\treturn fmt.Errorf(\"totalNumShards must be positive, got %d\", totalNumShards)\n}","typeGuard":null,"tryCatchPattern":"scaler, err := NewOwnershipBasedQuotaScaler(counter, n, cb)\nif errors.Is(err, ErrNonPositiveTotalNumShards) {\n\t// fix configuration before proceeding; do not start with invalid scaling\n}","preventionTips":["Validate numHistoryShards config at startup","Fail fast if shard counter reports a non-positive total","Add a unit test constructing the scaler with every configured value source"],"tags":["go","quota","configuration","history-service"],"backgroundTag":"invalid-config-value","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}