Skip to content
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

fairqueuing implementation with unit tests #84544

Closed
wants to merge 4 commits into from

Conversation

aaron-prindle
Copy link
Contributor

@aaron-prindle aaron-prindle commented Oct 30, 2019

What type of PR is this?
/kind feature

What this PR does / why we need it:
Implement package for fair queuing algorithm, which will be used in KEP priority and fariness.

Special notes for your reviewer:
This PR includes work from the feature/rate-limiting-branch from these PRs:
#80786, #81621, #81707, #81788

This PR only adds the fair queuing logic and required libraries. The wiring up the fair queuing into a full request manager is not in this PR.
Does this PR introduce a user-facing change?:

NONE

Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:

- [KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/20190228-priority-and-fairness.md

@k8s-ci-robot k8s-ci-robot added release-note-none Denotes a PR that doesn't merit a release note. kind/feature Categorizes issue or PR as related to a new feature. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. area/apiserver area/dependency Issues or PRs related to dependency changes sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. and removed needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. labels Oct 30, 2019
@MikeSpreitzer
Copy link
Member

/cc @MikeSpreitzer

@mars1024
Copy link
Member

/cc @mars1024 @yue9944882

Copy link
Member

@lavalamp lavalamp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I made it through the rest of this file.

func (qs *queueSet) enqueue(request *fq.Request) {
queue := request.Queue
queue.Enqueue(request)
qs.updateQueueVirtualStartTime(request, queue)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have expected this update to happen when we start executing the request, not when we enqueue it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you are saying a pointer to the KEP is not good enough, we have to copy the reasoning from there into the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect this can be explained in one sentence and that sentence would be super enlightening. Worst case, the sentence is "there's no possible short explanation of this, see the KEP"; that would tell me that there's something major missing in my mental model of how this code works.

But I expect that a useful sentence like "The virtual clock on a queue starts when the first request is queued (and e.g. not when the request begins to execute) because ____." can be written.

// https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/20190228-priority-and-fairness.md#dispatching
func (qs *queueSet) updateQueueVirtualStartTime(packet *fq.Request, queue *fq.Queue) {
// When a request arrives to an empty queue with no requests executing:
// len(queue.Requests) == 1 as enqueue has just happened prior (vs == 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make the comment explain why this is to be done? ("it's in the KEP" is not an explanation!)

}

// dequeue dequeues a request from the queueSet
func (qs *queueSet) dequeue() (*fq.Request, bool) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+"Locked"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice that you did not mention this on all internal methods that (a) must be called with the lock held and (b) do not have "Locked" in their name. What is your opinion on how to mark locking constraints on methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I didn't comment everywhere is simple: I reviewed in chunks and especially at first wasn't aware that there was a lock that some things needed to hold. It's best if the convention is universally applied.

if !ok {
return nil, false
}
qs.counter.Add(1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why shouldn't the thread reading from the channel do this?

If the answer is some variation of "locking" then why shouldn't this be done by where queue.RequestsExecuting and qs.numRequestsEnqueued are updated in dequeue?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "this" you mean increment the counter? That is for the same reason that the counter is incremented before forking a goroutine: (a) this is the code responsible for making another goroutine active and (b) doing the increment later might be too late.

r.Queue.VirtualStart -= qs.estimatedServiceTime - S

// request has finished, remove from requests executing
r.Queue.RequestsExecuting--
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we decrement qs.counter here? (or defer such a decrement?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, there is no goroutine ending or going idle here.

@lavalamp
Copy link
Member

I'm primarily bothered by two things, which may be the same thing:

  • Why not have a goroutine doing the enqueueings and dequeueings instead of tacking that work on at various places in existing goroutines?
  • Can we find more principled ways/places to increment/decrement the various counters? I'm not convinced everything is paired and I'm not sure how to convince myself. (That is, I'm like 80% sure it's proper--I didn't see anything obviously wrong--but I want to be 99.99%+ convinced.)

}
}

// TestNoRestraint should fail because the dummy QueueSet exercises no control
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly should fail? Not the whole test, or we wouldn't check it in?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the first test function written, before we had a real implementation of QueueSet. Now that we do, this test is relatively uninteresting. To be fair, it does test the test.

exerciseQueueSetUniformScenario(t, qs, []uniformClient{
{1001001001, 5, 100, time.Second, time.Second},
}, time.Second*10, true, false, clk, counter)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we test changing config anywhere in this file? That seems important, both adding and removing queues, and whatever else is needed.

I would feel a lot more confident about the locking / counting if we could make a "thrash" test, e.g. use a real clock and simulate a bunch of random requests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a randomized tester with config changes would be a good add. It would not need to use a real clock.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some testing with a real clock adds assurance that there's no deadlocks hiding in the code.

@MikeSpreitzer
Copy link
Member

MikeSpreitzer commented Nov 12, 2019

Here is a suggestion of a way to cleanly package up the union-of-unblocks logic. Define an internal (i.e., must be accessed only while holding the QueueSet's lock) abstraction like the following.

type initiallyUnset struct {/* a GoRoutineCounter, a condition variable, a count of waiting goroutines , `isSet bool`, `value interface{}` */}

func (iu *initiallyUnset) set(value interface{}) {/* set the value, signal the CV, add count to GoRoutineCounter, zero the count (only to highlight symmetry) */}

func (iu *initiallyUnset) get() interface{} {/* if not set yet then increment count and decrement GoRoutineCounter then wait on the CV */}

@BenTheElder
Copy link
Member

/uncc
Lavalamp can approve deps and there's a lot of other code still under review.

@MikeSpreitzer
Copy link
Member

BTW, since we are not super close on this one yet, I suggest we not do force-push, so that it is easier for reviewers to find the recent deltas.

}
}
klog.V(5).Infof("request timed out after being enqueued\n")
metrics.AddReject(qs.config.Name, "time-out")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we need to decrement the counter here? Wouldn't it have been incremented prior to sending something down the dequeue channel?

@k8s-ci-robot k8s-ci-robot added sig/cluster-lifecycle Categorizes an issue or PR as relevant to SIG Cluster Lifecycle. sig/instrumentation Categorizes an issue or PR as relevant to SIG Instrumentation. labels Nov 13, 2019
@MikeSpreitzer
Copy link
Member

I folded the latest changes from here into #85192 .

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: aaron-prindle
To complete the pull request process, please assign liggitt
You can assign the PR to them by writing /assign @liggitt in a comment when ready.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the sig/scheduling Categorizes an issue or PR as relevant to SIG Scheduling. label Nov 13, 2019
@k8s-ci-robot
Copy link
Contributor

@aaron-prindle: The following tests failed, say /retest to rerun them all:

Test name Commit Details Rerun command
pull-kubernetes-bazel-test 009a6bc link /test pull-kubernetes-bazel-test
pull-kubernetes-e2e-gce-device-plugin-gpu 009a6bc link /test pull-kubernetes-e2e-gce-device-plugin-gpu
pull-kubernetes-e2e-gce 009a6bc link /test pull-kubernetes-e2e-gce
pull-kubernetes-verify 009a6bc link /test pull-kubernetes-verify
pull-kubernetes-e2e-kind 009a6bc link /test pull-kubernetes-e2e-kind
pull-kubernetes-kubemark-e2e-gce-big 009a6bc link /test pull-kubernetes-kubemark-e2e-gce-big
pull-kubernetes-e2e-gce-100-performance 009a6bc link /test pull-kubernetes-e2e-gce-100-performance

Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

@MikeSpreitzer
Copy link
Member

This should be closed, #85192 has merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/apiserver area/dependency Issues or PRs related to dependency changes cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/feature Categorizes issue or PR as related to a new feature. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. release-note-none Denotes a PR that doesn't merit a release note. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. sig/cluster-lifecycle Categorizes an issue or PR as relevant to SIG Cluster Lifecycle. sig/instrumentation Categorizes an issue or PR as relevant to SIG Instrumentation. sig/scheduling Categorizes an issue or PR as relevant to SIG Scheduling. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants