-
Notifications
You must be signed in to change notification settings - Fork 801
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add invariant manager #3263
Add invariant manager #3263
Conversation
@@ -140,13 +140,13 @@ func Open(state int) bool { | |||
func DeleteExecution( | |||
exec *Execution, | |||
pr PersistenceRetryer, | |||
) FixResult { | |||
) *FixResult { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Seems like other places are still returning the struct directly, instead of a pointer. Probably make them consistent?
I think returning struct is fine here as the struct itself is not big. Returning a struct will cause the object to be allocated on the heap and increase GC pressure.
func (o *openCurrentExecution) Fix(execution common.Execution) common.FixResult { | ||
return common.DeleteExecution(&execution, o.pr) | ||
func (o *openCurrentExecution) Fix(execution common.Execution, resources *common.InvariantResourceBag) common.FixResult { | ||
fixResult, checkResult := checkBeforeFix(o, execution, resources) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am confused. To me, if a check is needed before fix, then it's the caller's responsibility to first call Check()
and then Fix()
. I think putting them together inside Fix()
couples the logic and makes the code a little bit inflexible.
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
// SOFTWARE. | ||
|
||
package shard |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this shard
package for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 I think this can live in the invariants
package
} | ||
} | ||
|
||
// InvariantTypes returns sorted list of all invariants that manager will run. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
returns sorted list of all invariants types that manager will run
c6baf2f
to
906736b
Compare
|
||
// NewInvariantManager handles running a collection of invariants according to the policy provided. | ||
// InvariantManager takes care of ensuring invariants are run in their correct dependency order. | ||
func NewInvariantManager( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to overengineer this, but I have a slightly different idea about how we can implement this part about Policies and Manager and wanted to hear what you think about it:
- Don't limit the manager to a single policy. Make it such that it defaults to running all policies unless a filter says otherwise. We can keep this filter in a config without hard-coding in the application.
- Define a collection of all policies (maybe we shouldn't call this
policy
to avoid confusion), which would act like test cases, where each of them consists of an ordered list of Invariants to test a meaningful case end-to-end. Ideally, these policies should never be too long. An invariant should be able to appear in multiple policies. There shouldn't be any order/dependency requirements between policies in the collection. - Manager should take a list of policies to include or exclude (the config I mentioned above), and decide to run all Policies one by one, without worrying about their order. As it checks for the invariants in each policy, it can populate a map of invariant types => check results, and use that map to avoid running a given invariant repeatedly across multiple policies. At the end of the iterations, all invariants that are involved in one or more included policies will get executed exactly once.
If we follow the design above, we get the following benefits:
- Seperation of concerns between invariants-related logic vs. the overall Scanner. Scanner interacts with a single Manager to run the tests, and the only thing it provides is the config that tells which policies should run.
- More flexibility in the config without adding complexity. Rather than specifying a predefined policy to run, we can precisely tell what policies we want to run (and/or what policies we don't). Since the policies are not ordered or dependent on each other, the config is simple to manage.
- We avoid running the same invariant repeatedly if it was already covered by an earlier policy, without adding a complex invariant sorting algorithm to the mix.
For the number of invariants & policies you currently have in the PR, my proposal is definitely an overkill. However, if we eventually have a dozen or more invariants that are spread over multiple overlapping policies, it should scale better in terms of maintainability.
3f98b86
to
3a99bef
Compare
169f7c2
to
4ea9298
Compare
Potential risks