Hi, This series adds cgroup awareness to unbound workqueues. This is the second version since Bandan Das's post from a few years ago[1]. The design is completely new, but the code is still in development and I'm posting early to get feedback on the design. Is this a good direction? Thanks, Daniel Summary ------- Cgroup controllers don't throttle workqueue workers for the most part, so resource-intensive works run unchecked. Fix it by adding a new type of work that causes the assigned worker to attach to the given cgroup. Motivation ---------- Workqueue workers are currently always attached to root cgroups. If a task in a child cgroup queues a resource-intensive work, the resource limits of the child cgroup generally don't throttle the worker, with some exceptions such as writeback. My use case for this work is kernel multithreading, the series formerly known as ktask[2] that I'm now trying to combine with padata according to feedback from the last post. Helper threads in a multithreaded job may consume lots of resources that aren't properly accounted to the cgroup of the task that started the job. Basic Idea ---------- I know of two basic ways to fix this, with other ideas welcome. They both use the existing cgroup migration path to move workers to different cgroups. #1 Maintain per-cgroup worker pools and queue works on these pools. A worker in the pool is migrated once to the pool's assigned cgroup when the worker is first created. These days, users can have hundreds or thousands of cgroups on their systems, which means that #1 could cause as many workers to be created across the pools, bringing back the problems of MT workqueues.[3] The concurrency level could be managed across the pools, but I don't see how to avoid thrashing on worker creation and destruction with even demand for workers across cgroups. So #1 doesn't seem like the right way forward. #2 Migrate a worker to the desired cgroup before it runs the work. Worker pools are shared across cgroups, and workers migrate to different cgroups as needed. #2 has some issues of its own, namely cgroup_mutex and cgroup_threadgroup_rwsem. These prevent concurrent worker migrations, so for this to work scalably, these locks should be fixed. css_set_lock and controller-specific locks may then also be a problem. Nevertheless, #2 keeps the total number of workers low to accommodate systems with many cgroups. This RFC implements #2. If the design looks good, I can start working on fixing the locks, and I'd be thrilled if others wanted to help with this. A third alternative arose late in the development of this series that takes inspiration from proxy execution, in which a task's scheduling context and execution context are treated separately[4]. The idea is to allow a proxy task to temporarily assume the cgroup characteristics of another task so that it can use the other task's cgroup-related task_struct fields. The worker avoids the performance and scalability cost of the migration path, but it also doesn't run the attach callbacks, so controllers wouldn't work as designed without adding special logic in various places to account for this situation. That doesn't sound immediately appealing, but I haven't thought about it for very long. Data Structures --------------- Cgroup awareness is implemented per work with a new type of work item: struct cgroup_work { struct work_struct work; #ifdef CONFIG_CGROUPS struct cgroup *cgroup; #endif }; The cgroup field contains the cgroup to which the assigned worker should attach. A new type is used so only those users who want cgroup awareness incur the space overhead of the cgroup pointer. This feature is supported only for cgroups on the default hierarchy, so one cgroup pointer is sufficient. Workqueues may be created with the WQ_CGROUP flag. The flag means that cgroup works, and only cgroup works, may be queued on this workqueue. Cgroup works aren't allowed to be queued on !WQ_CGROUP workqueues. This separation avoids the issues that come from cgroup_works and regular works being queued together, such as distinguishing between the two on a worklist, which probably means adding a new work data bit causing increased memory usage from higher pool_workqueue alignment, or creating multiple worklists and dealing fairly with, "which worklist do I pick from next?" Migrating Kernel Threads ------------------------ Migrated worker pids appear in cgroup.procs and cgroup.threads, and they block cgroup destruction (cgroup_rmdir) just as user tasks do. To alleviate this somewhat, workers that have finished their work migrate themselves back to the root cgroup before sleeping. In addition, it's probably best to allow userland to destroy a cgroup when only kernel threads remain (no user tasks left), with destruction finishing in the background once all kernel threads have been migrated out. The reason is, it's consistent with current cgroup behavior in which management apps, libraries, etc may expect destruction to succeed when all known tasks have been moved out. So that's tentatively on my TODO, but I'm curious what people think. It's possible for task migration to fail for several reasons. On failure, the worker tries migrating itself to the root cgroup. In case _this_ fails, the code currently throws a warning, but it seems best to design this so that migrating a kernel thread to the root can't fail. Otherwise, with both failures, we account work to an unrelated, random cgroup. Priority Inversion ------------------ One concern with cgroup-aware workers may be priority inversion[5]. I couldn't find where this was discussed in detail, but it seems the issue is that a worker could be throttled by some resource limit from its attached cgroup, causing other work items' execution to be delayed a long time. However, this doesn't seem to be a problem because of how worker pools are managed. There's an invariant that at least one idle worker should exist in a pool before a worker begins processing works, so that there will be at least one worker per work item, avoiding the inversion. It's possible that works from a large number of different resource-constrained cgroups could cause as many workers to be created, with creation eventually failing due for example to pid exhaustion, but in that extreme case workqueue will retry repeatedly with a CREATE_COOLDOWN timeout. This seems good enough, but I'm open to other ideas. Testing ------- A little, not a lot. I've sanity-checked that some controllers throttle workers as expected (memory, cpu, pids), "believe" rdma should work, haven't looked at io yet, and know cpuset is broken. For cpuset, I need to fix ->can_attach() for bound kthreads and reconcile the controller's cpumasks with worker cpumasks. In one experiment on a large Xeon server, a kernel thread was migrated 2M times back and forth between two cgroups. The mean time per migration was 1 usec, so cgroup-aware work items should take much longer than that for the migration to be worth it. TODO ---- - scale cgroup_mutex and cgroup_threadcgroup_rwsem - support the cpuset controller, and reconcile that with workqueue NUMA awareness and worker_pool cpumasks - support the io controller - make kernel thread migration to the root cgroup always succeed - maybe allow userland to destroy a cgroup with only kernel threads Dependencies ------------ This series is against 5.1 plus some kernel multithreading patches (formerly ktask). A branch with everything is available at git://oss.oracle.com/git/linux-dmjordan.git cauwq-rfc-v2 The multithreading patches don't incorporate some of the feedback from the last post[2] (yet) because I'm in the process of addressing the larger design comments. [1] http://lkml.kernel.org/r/1458339291-4093-1-git-send-email-bsd@xxxxxxxxxx [2] https://lore.kernel.org/linux-mm/20181105165558.11698-1-daniel.m.jordan@xxxxxxxxxx/ [3] https://lore.kernel.org/lkml/4C17C598.7070303@xxxxxxxxxx/ [4] https://lore.kernel.org/lkml/20181009092434.26221-1-juri.lelli@xxxxxxxxxx/ [5] https://lore.kernel.org/netdev/4BFE9ABA.6030907@xxxxxxxxxx/ Daniel Jordan (5): cgroup: add cgroup v2 interfaces to migrate kernel threads workqueue, cgroup: add cgroup-aware workqueues workqueue, memcontrol: make memcg throttle workqueue workers workqueue, cgroup: add test module ktask, cgroup: attach helper threads to the master thread's cgroup include/linux/cgroup.h | 43 +++ include/linux/workqueue.h | 85 +++++ kernel/cgroup/cgroup-internal.h | 1 - kernel/cgroup/cgroup.c | 48 ++- kernel/ktask.c | 32 +- kernel/workqueue.c | 263 +++++++++++++- kernel/workqueue_internal.h | 50 +++ lib/Kconfig.debug | 12 + lib/Makefile | 1 + lib/test_cgroup_workqueue.c | 325 ++++++++++++++++++ mm/memcontrol.c | 26 +- .../selftests/cgroup_workqueue/Makefile | 9 + .../testing/selftests/cgroup_workqueue/config | 1 + .../cgroup_workqueue/test_cgroup_workqueue.sh | 104 ++++++ 14 files changed, 963 insertions(+), 37 deletions(-) create mode 100644 lib/test_cgroup_workqueue.c create mode 100644 tools/testing/selftests/cgroup_workqueue/Makefile create mode 100644 tools/testing/selftests/cgroup_workqueue/config create mode 100755 tools/testing/selftests/cgroup_workqueue/test_cgroup_workqueue.sh base-commit: e93c9c99a629c61837d5a7fc2120cd2b6c70dbdd prerequisite-patch-id: 253830d9ec7ed8f9d10127c1bc61f2489c40f042 prerequisite-patch-id: 0fa4fe0d879ae76f8e16d15982d799d84f67bee3 prerequisite-patch-id: e2e8229b9d1a1efa75262910a597902e136a6214 prerequisite-patch-id: f67900739fe811de1d4d06e19a5aa180b46396d8 prerequisite-patch-id: 7349e563091d065d4ace565f3daca139d7d470ad prerequisite-patch-id: 6cd37c09bb0902519d574080b5c56d61755935fe prerequisite-patch-id: a07d6676fbb5ed07486a3160e6eced91ecef1842 prerequisite-patch-id: 17baa0481806fc48dcb32caffb973120ac599c8d prerequisite-patch-id: 6e629bbeb6efdd69aa733731bc91690346f26f21 prerequisite-patch-id: 59630f99305aa371e024b652e754d67614481c29 prerequisite-patch-id: 46c713fed894a530e9a0a83ca2a7c1ae2c787a5f prerequisite-patch-id: 4b233711a8bdd1ef9fa82e364f07595526036ff4 prerequisite-patch-id: 9caefc8d5d48d6ec42bad4336fa38a28118296da prerequisite-patch-id: 04d008e4ffbe499ebc5b466b7777dabff9a6c77e prerequisite-patch-id: 396a88dad48473d4b54f539fadeb7d601020098d -- 2.21.0