diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-07 05:48:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-07 05:48:48 +0000 |
commit | ef24de24a82fe681581cc130f342363c47c0969a (patch) | |
tree | 0d494f7e1a38b95c92426f58fe6eaa877303a86c /vendor/jobserver | |
parent | Releasing progress-linux version 1.74.1+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-ef24de24a82fe681581cc130f342363c47c0969a.tar.xz rustc-ef24de24a82fe681581cc130f342363c47c0969a.zip |
Merging upstream version 1.75.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/jobserver')
-rw-r--r-- | vendor/jobserver/.cargo-checksum.json | 2 | ||||
-rw-r--r-- | vendor/jobserver/Cargo.toml | 2 | ||||
-rw-r--r-- | vendor/jobserver/LICENSE-APACHE | 402 | ||||
-rw-r--r-- | vendor/jobserver/LICENSE-MIT | 50 | ||||
-rw-r--r-- | vendor/jobserver/README.md | 78 | ||||
-rw-r--r-- | vendor/jobserver/src/error.rs | 84 | ||||
-rw-r--r-- | vendor/jobserver/src/lib.rs | 1140 | ||||
-rw-r--r-- | vendor/jobserver/src/unix.rs | 908 | ||||
-rw-r--r-- | vendor/jobserver/src/wasm.rs | 191 | ||||
-rw-r--r-- | vendor/jobserver/src/windows.rs | 536 | ||||
-rw-r--r-- | vendor/jobserver/tests/client-of-myself.rs | 118 | ||||
-rw-r--r-- | vendor/jobserver/tests/client.rs | 396 | ||||
-rw-r--r-- | vendor/jobserver/tests/helper.rs | 154 | ||||
-rw-r--r-- | vendor/jobserver/tests/make-as-a-client.rs | 162 | ||||
-rw-r--r-- | vendor/jobserver/tests/server.rs | 362 |
15 files changed, 2386 insertions, 2199 deletions
diff --git a/vendor/jobserver/.cargo-checksum.json b/vendor/jobserver/.cargo-checksum.json index 734fd6db9..ec4f23bef 100644 --- a/vendor/jobserver/.cargo-checksum.json +++ b/vendor/jobserver/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"0f712c94e98313fc5833521e1dc0c42a57e5603ea54d1e05a004b95b52fb39b7","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","README.md":"07d8d79f8f6b6a94321fe8db78d26ed409de47cee49290947bd6bbfa29d05e9c","src/lib.rs":"be789fda33a51375fcc87e4bf9bf1256930d718f698f700bbec5e335c83e0659","src/unix.rs":"1e1efc5cd1f381fea83bdaaee474ad3530b396305a9bbdc9da08b0c36baced31","src/wasm.rs":"65d3d8ed45972b4459581505906481d32a50d2f7514cd7ff2a595fceeaa672f0","src/windows.rs":"8e0fa3ab29757d809d4fa03c8101870435ce8c4ceaebe491df3144d62fe0aaaf","tests/client-of-myself.rs":"ca09bf398f69df4bac1730999e954dbbc3faf3c6512678c136e0938e7e9cd0ab","tests/client.rs":"d4745cdd650c86d19bc81f6c9b35df498996deffb86ae6412ad040af96a19183","tests/helper.rs":"c0e6c00eaf849295d8ec23e374690b6645c0f7d993e91abf7ad53ac960f71762","tests/make-as-a-client.rs":"8be1f3fef1e9e65c7904dbaa04364bf0f44e9deab84a2a247a5a94b5cf0df9bc","tests/server.rs":"da15bf12e1df1883f660892b996c9e0d92485aace3f7b50ee70c4a8e6deae8da"},"package":"936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"}
\ No newline at end of file +{"files":{"Cargo.toml":"7236550dc71dfeecdd220976fd14dff13c99219c927b0f45a09900ae778c3764","LICENSE-APACHE":"7cfd738c53d61c79f07e348f622bf7707c9084237054d37fbe07788a75f5881c","LICENSE-MIT":"29c9533fc9a32ee529e928bcb12308f0745d237fdea80d83c0258f23243d65d1","README.md":"bf650d48029473e7e2d93b4a6bb46ab08c4ae60a327dc294351372685dcedfa5","src/error.rs":"5ffc1e2e35c25266a521a947c50ebe6292a6d88142235f404a5dc79bf393f8b5","src/lib.rs":"8eba0ff1829e68aec642fdb0e7928e199dfd2109d6c4d797bdfddbf4c08585ef","src/unix.rs":"64e559fac928156468490258728c89fbda75cd81e6c70baf05f5355b2c2623ff","src/wasm.rs":"791974e745b72f8133a55a9d15b8b6efd6513d921ac58f4ddc4e6035cd87a9af","src/windows.rs":"6b9faff05d572716d5ac1387300cf0cc8e0f0cfe80100bc2007572b12863d170","tests/client-of-myself.rs":"0ea04ff17614e248e281e5e730ecc615f1e310aefac42a7f95da2dcfb42c1c5e","tests/client.rs":"770ac4065207985c1427ededb95d6dcb341ca26a36aad65f4f1e245d09f28677","tests/helper.rs":"1e970fef77b828948e453d46487c6195b0e7774d6c14f16b32623b3ec6e2186c","tests/make-as-a-client.rs":"4f96a30f24c0a93e003d6107ccc767549ef18a7b0d022dbbab8dc70348f698ee","tests/server.rs":"231493b95789b62987f2790c0f4df2a23db27a3a66dacb20987c67e7203cdd6c"},"package":"8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"}
\ No newline at end of file diff --git a/vendor/jobserver/Cargo.toml b/vendor/jobserver/Cargo.toml index 938371008..851b27ca2 100644 --- a/vendor/jobserver/Cargo.toml +++ b/vendor/jobserver/Cargo.toml @@ -12,7 +12,7 @@ [package] edition = "2018" name = "jobserver" -version = "0.1.26" +version = "0.1.27" authors = ["Alex Crichton <alex@alexcrichton.com>"] description = """ An implementation of the GNU make jobserver for Rust diff --git a/vendor/jobserver/LICENSE-APACHE b/vendor/jobserver/LICENSE-APACHE index 16fe87b06..f47c94114 100644 --- a/vendor/jobserver/LICENSE-APACHE +++ b/vendor/jobserver/LICENSE-APACHE @@ -1,201 +1,201 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/vendor/jobserver/LICENSE-MIT b/vendor/jobserver/LICENSE-MIT index 39e0ed660..a22128a47 100644 --- a/vendor/jobserver/LICENSE-MIT +++ b/vendor/jobserver/LICENSE-MIT @@ -1,25 +1,25 @@ -Copyright (c) 2014 Alex Crichton - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +Copyright (c) 2014 Alex Crichton
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/vendor/jobserver/README.md b/vendor/jobserver/README.md index 7b06c3eb0..affa97fb6 100644 --- a/vendor/jobserver/README.md +++ b/vendor/jobserver/README.md @@ -1,39 +1,39 @@ -# jobserver-rs - -An implementation of the GNU make jobserver for Rust - -[![Crates.io](https://img.shields.io/crates/v/jobserver.svg?maxAge=2592000)](https://crates.io/crates/jobserver) - -[Documentation](https://docs.rs/jobserver) - -## Usage - -First, add this to your `Cargo.toml`: - -```toml -[dependencies] -jobserver = "0.1" -``` - -Next, add this to your crate: - -```rust -extern crate jobserver; -``` - -# License - -This project is licensed under either of - - * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or - http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in jobserver-rs by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. +# jobserver-rs
+
+An implementation of the GNU make jobserver for Rust
+
+[![Crates.io](https://img.shields.io/crates/v/jobserver.svg?maxAge=2592000)](https://crates.io/crates/jobserver)
+
+[Documentation](https://docs.rs/jobserver)
+
+## Usage
+
+First, add this to your `Cargo.toml`:
+
+```toml
+[dependencies]
+jobserver = "0.1"
+```
+
+Next, add this to your crate:
+
+```rust
+extern crate jobserver;
+```
+
+# License
+
+This project is licensed under either of
+
+ * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
+ http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or
+ http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in jobserver-rs by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
diff --git a/vendor/jobserver/src/error.rs b/vendor/jobserver/src/error.rs new file mode 100644 index 000000000..9f95f18b8 --- /dev/null +++ b/vendor/jobserver/src/error.rs @@ -0,0 +1,84 @@ +#[cfg(unix)]
+type RawFd = std::os::fd::RawFd;
+#[cfg(not(unix))]
+type RawFd = std::convert::Infallible;
+
+/// Error type for `from_env_ext` function.
+#[derive(Debug)]
+pub struct FromEnvError {
+ pub(crate) inner: FromEnvErrorInner,
+}
+
+/// Kind of an error returned from `from_env_ext` function.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum FromEnvErrorKind {
+ /// There is no environment variable that describes jobserver to inherit.
+ NoEnvVar,
+ /// There is no jobserver in the environment variable.
+ /// Variables associated with Make can be used for passing data other than jobserver info.
+ NoJobserver,
+ /// Cannot parse jobserver environment variable value, incorrect format.
+ CannotParse,
+ /// Cannot open path or name from the jobserver environment variable value.
+ CannotOpenPath,
+ /// Cannot open file descriptor from the jobserver environment variable value.
+ CannotOpenFd,
+ /// File descriptor from the jobserver environment variable value is not a pipe.
+ NotAPipe,
+ /// Jobserver inheritance is not supported on this platform.
+ Unsupported,
+}
+
+impl FromEnvError {
+ /// Get the error kind.
+ pub fn kind(&self) -> FromEnvErrorKind {
+ match self.inner {
+ FromEnvErrorInner::NoEnvVar => FromEnvErrorKind::NoEnvVar,
+ FromEnvErrorInner::NoJobserver => FromEnvErrorKind::NoJobserver,
+ FromEnvErrorInner::CannotParse(_) => FromEnvErrorKind::CannotParse,
+ FromEnvErrorInner::CannotOpenPath(..) => FromEnvErrorKind::CannotOpenPath,
+ FromEnvErrorInner::CannotOpenFd(..) => FromEnvErrorKind::CannotOpenFd,
+ FromEnvErrorInner::NotAPipe(..) => FromEnvErrorKind::NotAPipe,
+ FromEnvErrorInner::Unsupported => FromEnvErrorKind::Unsupported,
+ }
+ }
+}
+
+impl std::fmt::Display for FromEnvError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match &self.inner {
+ FromEnvErrorInner::NoEnvVar => write!(f, "there is no environment variable that describes jobserver to inherit"),
+ FromEnvErrorInner::NoJobserver => write!(f, "there is no `--jobserver-fds=` or `--jobserver-auth=` in the environment variable"),
+ FromEnvErrorInner::CannotParse(s) => write!(f, "cannot parse jobserver environment variable value: {s}"),
+ FromEnvErrorInner::CannotOpenPath(s, err) => write!(f, "cannot open path or name {s} from the jobserver environment variable value: {err}"),
+ FromEnvErrorInner::CannotOpenFd(fd, err) => write!(f, "cannot open file descriptor {fd} from the jobserver environment variable value: {err}"),
+ FromEnvErrorInner::NotAPipe(fd, None) => write!(f, "file descriptor {fd} from the jobserver environment variable value is not a pipe"),
+ FromEnvErrorInner::NotAPipe(fd, Some(err)) => write!(f, "file descriptor {fd} from the jobserver environment variable value is not a pipe: {err}"),
+ FromEnvErrorInner::Unsupported => write!(f, "jobserver inheritance is not supported on this platform"),
+ }
+ }
+}
+impl std::error::Error for FromEnvError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match &self.inner {
+ FromEnvErrorInner::CannotOpenPath(_, err) => Some(err),
+ FromEnvErrorInner::NotAPipe(_, Some(err)) | FromEnvErrorInner::CannotOpenFd(_, err) => {
+ Some(err)
+ }
+ _ => None,
+ }
+ }
+}
+
+#[allow(dead_code)]
+#[derive(Debug)]
+pub(crate) enum FromEnvErrorInner {
+ NoEnvVar,
+ NoJobserver,
+ CannotParse(String),
+ CannotOpenPath(String, std::io::Error),
+ CannotOpenFd(RawFd, std::io::Error),
+ NotAPipe(RawFd, Option<std::io::Error>),
+ Unsupported,
+}
diff --git a/vendor/jobserver/src/lib.rs b/vendor/jobserver/src/lib.rs index cd0cdd749..bfa7980c4 100644 --- a/vendor/jobserver/src/lib.rs +++ b/vendor/jobserver/src/lib.rs @@ -1,544 +1,596 @@ -//! An implementation of the GNU make jobserver. -//! -//! This crate is an implementation, in Rust, of the GNU `make` jobserver for -//! CLI tools that are interoperating with make or otherwise require some form -//! of parallelism limiting across process boundaries. This was originally -//! written for usage in Cargo to both (a) work when `cargo` is invoked from -//! `make` (using `make`'s jobserver) and (b) work when `cargo` invokes build -//! scripts, exporting a jobserver implementation for `make` processes to -//! transitively use. -//! -//! The jobserver implementation can be found in [detail online][docs] but -//! basically boils down to a cross-process semaphore. On Unix this is -//! implemented with the `pipe` syscall and read/write ends of a pipe and on -//! Windows this is implemented literally with IPC semaphores. Starting from -//! GNU `make` version 4.4, named pipe becomes the default way in communication -//! on Unix. This crate also supports that feature in the sense of inheriting -//! and forwarding the correct environment. -//! -//! The jobserver protocol in `make` also dictates when tokens are acquired to -//! run child work, and clients using this crate should take care to implement -//! such details to ensure correct interoperation with `make` itself. -//! -//! ## Examples -//! -//! Connect to a jobserver that was set up by `make` or a different process: -//! -//! ```no_run -//! use jobserver::Client; -//! -//! // See API documentation for why this is `unsafe` -//! let client = match unsafe { Client::from_env() } { -//! Some(client) => client, -//! None => panic!("client not configured"), -//! }; -//! ``` -//! -//! Acquire and release token from a jobserver: -//! -//! ```no_run -//! use jobserver::Client; -//! -//! let client = unsafe { Client::from_env().unwrap() }; -//! let token = client.acquire().unwrap(); // blocks until it is available -//! drop(token); // releases the token when the work is done -//! ``` -//! -//! Create a new jobserver and configure a child process to have access: -//! -//! ``` -//! use std::process::Command; -//! use jobserver::Client; -//! -//! let client = Client::new(4).expect("failed to create jobserver"); -//! let mut cmd = Command::new("make"); -//! client.configure(&mut cmd); -//! ``` -//! -//! ## Caveats -//! -//! This crate makes no attempt to release tokens back to a jobserver on -//! abnormal exit of a process. If a process which acquires a token is killed -//! with ctrl-c or some similar signal then tokens will not be released and the -//! jobserver may be in a corrupt state. -//! -//! Note that this is typically ok as ctrl-c means that an entire build process -//! is being torn down, but it's worth being aware of at least! -//! -//! ## Windows caveats -//! -//! There appear to be two implementations of `make` on Windows. On MSYS2 one -//! typically comes as `mingw32-make` and the other as `make` itself. I'm not -//! personally too familiar with what's going on here, but for jobserver-related -//! information the `mingw32-make` implementation uses Windows semaphores -//! whereas the `make` program does not. The `make` program appears to use file -//! descriptors and I'm not really sure how it works, so this crate is not -//! compatible with `make` on Windows. It is, however, compatible with -//! `mingw32-make`. -//! -//! [docs]: http://make.mad-scientist.net/papers/jobserver-implementation/ - -#![deny(missing_docs, missing_debug_implementations)] -#![doc(html_root_url = "https://docs.rs/jobserver/0.1")] - -use std::env; -use std::io; -use std::process::Command; -use std::sync::{Arc, Condvar, Mutex, MutexGuard}; - -#[cfg(unix)] -#[path = "unix.rs"] -mod imp; -#[cfg(windows)] -#[path = "windows.rs"] -mod imp; -#[cfg(not(any(unix, windows)))] -#[path = "wasm.rs"] -mod imp; - -/// A client of a jobserver -/// -/// This structure is the main type exposed by this library, and is where -/// interaction to a jobserver is configured through. Clients are either created -/// from scratch in which case the internal semphore is initialied on the spot, -/// or a client is created from the environment to connect to a jobserver -/// already created. -/// -/// Some usage examples can be found in the crate documentation for using a -/// client. -/// -/// Note that a `Client` implements the `Clone` trait, and all instances of a -/// `Client` refer to the same jobserver instance. -#[derive(Clone, Debug)] -pub struct Client { - inner: Arc<imp::Client>, -} - -/// An acquired token from a jobserver. -/// -/// This token will be released back to the jobserver when it is dropped and -/// otherwise represents the ability to spawn off another thread of work. -#[derive(Debug)] -pub struct Acquired { - client: Arc<imp::Client>, - data: imp::Acquired, - disabled: bool, -} - -impl Acquired { - /// This drops the `Acquired` token without releasing the associated token. - /// - /// This is not generally useful, but can be helpful if you do not have the - /// ability to store an Acquired token but need to not yet release it. - /// - /// You'll typically want to follow this up with a call to `release_raw` or - /// similar to actually release the token later on. - pub fn drop_without_releasing(mut self) { - self.disabled = true; - } -} - -#[derive(Default, Debug)] -struct HelperState { - lock: Mutex<HelperInner>, - cvar: Condvar, -} - -#[derive(Default, Debug)] -struct HelperInner { - requests: usize, - producer_done: bool, - consumer_done: bool, -} - -impl Client { - /// Creates a new jobserver initialized with the given parallelism limit. - /// - /// A client to the jobserver created will be returned. This client will - /// allow at most `limit` tokens to be acquired from it in parallel. More - /// calls to `acquire` will cause the calling thread to block. - /// - /// Note that the created `Client` is not automatically inherited into - /// spawned child processes from this program. Manual usage of the - /// `configure` function is required for a child process to have access to a - /// job server. - /// - /// # Examples - /// - /// ``` - /// use jobserver::Client; - /// - /// let client = Client::new(4).expect("failed to create jobserver"); - /// ``` - /// - /// # Errors - /// - /// Returns an error if any I/O error happens when attempting to create the - /// jobserver client. - pub fn new(limit: usize) -> io::Result<Client> { - Ok(Client { - inner: Arc::new(imp::Client::new(limit)?), - }) - } - - /// Attempts to connect to the jobserver specified in this process's - /// environment. - /// - /// When the a `make` executable calls a child process it will configure the - /// environment of the child to ensure that it has handles to the jobserver - /// it's passing down. This function will attempt to look for these details - /// and connect to the jobserver. - /// - /// Note that the created `Client` is not automatically inherited into - /// spawned child processes from this program. Manual usage of the - /// `configure` function is required for a child process to have access to a - /// job server. - /// - /// # Return value - /// - /// If a jobserver was found in the environment and it looks correct then - /// `Some` of the connected client will be returned. If no jobserver was - /// found then `None` will be returned. - /// - /// Note that on Unix the `Client` returned **takes ownership of the file - /// descriptors specified in the environment**. Jobservers on Unix are - /// implemented with `pipe` file descriptors, and they're inherited from - /// parent processes. This `Client` returned takes ownership of the file - /// descriptors for this process and will close the file descriptors after - /// this value is dropped. - /// - /// Additionally on Unix this function will configure the file descriptors - /// with `CLOEXEC` so they're not automatically inherited by spawned - /// children. - /// - /// # Safety - /// - /// This function is `unsafe` to call on Unix specifically as it - /// transitively requires usage of the `from_raw_fd` function, which is - /// itself unsafe in some circumstances. - /// - /// It's recommended to call this function very early in the lifetime of a - /// program before any other file descriptors are opened. That way you can - /// make sure to take ownership properly of the file descriptors passed - /// down, if any. - /// - /// It's generally unsafe to call this function twice in a program if the - /// previous invocation returned `Some`. - /// - /// Note, though, that on Windows it should be safe to call this function - /// any number of times. - pub unsafe fn from_env() -> Option<Client> { - let var = match env::var("CARGO_MAKEFLAGS") - .or_else(|_| env::var("MAKEFLAGS")) - .or_else(|_| env::var("MFLAGS")) - { - Ok(s) => s, - Err(_) => return None, - }; - let mut arg = "--jobserver-fds="; - let pos = match var.find(arg) { - Some(i) => i, - None => { - arg = "--jobserver-auth="; - match var.find(arg) { - Some(i) => i, - None => return None, - } - } - }; - - let s = var[pos + arg.len()..].split(' ').next().unwrap(); - imp::Client::open(s).map(|c| Client { inner: Arc::new(c) }) - } - - /// Acquires a token from this jobserver client. - /// - /// This function will block the calling thread until a new token can be - /// acquired from the jobserver. - /// - /// # Return value - /// - /// On successful acquisition of a token an instance of `Acquired` is - /// returned. This structure, when dropped, will release the token back to - /// the jobserver. It's recommended to avoid leaking this value. - /// - /// # Errors - /// - /// If an I/O error happens while acquiring a token then this function will - /// return immediately with the error. If an error is returned then a token - /// was not acquired. - pub fn acquire(&self) -> io::Result<Acquired> { - let data = self.inner.acquire()?; - Ok(Acquired { - client: self.inner.clone(), - data, - disabled: false, - }) - } - - /// Returns amount of tokens in the read-side pipe. - /// - /// # Return value - /// - /// Number of bytes available to be read from the jobserver pipe - /// - /// # Errors - /// - /// Underlying errors from the ioctl will be passed up. - pub fn available(&self) -> io::Result<usize> { - self.inner.available() - } - - /// Configures a child process to have access to this client's jobserver as - /// well. - /// - /// This function is required to be called to ensure that a jobserver is - /// properly inherited to a child process. If this function is *not* called - /// then this `Client` will not be accessible in the child process. In other - /// words, if not called, then `Client::from_env` will return `None` in the - /// child process (or the equivalent of `Child::from_env` that `make` uses). - /// - /// ## Platform-specific behavior - /// - /// On Unix and Windows this will clobber the `CARGO_MAKEFLAGS` environment - /// variables for the child process, and on Unix this will also allow the - /// two file descriptors for this client to be inherited to the child. - /// - /// On platforms other than Unix and Windows this panics. - pub fn configure(&self, cmd: &mut Command) { - cmd.env("CARGO_MAKEFLAGS", &self.mflags_env()); - self.inner.configure(cmd); - } - - /// Configures a child process to have access to this client's jobserver as - /// well. - /// - /// This function is required to be called to ensure that a jobserver is - /// properly inherited to a child process. If this function is *not* called - /// then this `Client` will not be accessible in the child process. In other - /// words, if not called, then `Client::from_env` will return `None` in the - /// child process (or the equivalent of `Child::from_env` that `make` uses). - /// - /// ## Platform-specific behavior - /// - /// On Unix and Windows this will clobber the `CARGO_MAKEFLAGS`, - /// `MAKEFLAGS` and `MFLAGS` environment variables for the child process, - /// and on Unix this will also allow the two file descriptors for - /// this client to be inherited to the child. - /// - /// On platforms other than Unix and Windows this panics. - pub fn configure_make(&self, cmd: &mut Command) { - let value = self.mflags_env(); - cmd.env("CARGO_MAKEFLAGS", &value); - cmd.env("MAKEFLAGS", &value); - cmd.env("MFLAGS", &value); - self.inner.configure(cmd); - } - - fn mflags_env(&self) -> String { - let arg = self.inner.string_arg(); - // Older implementations of make use `--jobserver-fds` and newer - // implementations use `--jobserver-auth`, pass both to try to catch - // both implementations. - format!("-j --jobserver-fds={0} --jobserver-auth={0}", arg) - } - - /// Converts this `Client` into a helper thread to deal with a blocking - /// `acquire` function a little more easily. - /// - /// The fact that the `acquire` function on `Client` blocks isn't always - /// the easiest to work with. Typically you're using a jobserver to - /// manage running other events in parallel! This means that you need to - /// either (a) wait for an existing job to finish or (b) wait for a - /// new token to become available. - /// - /// Unfortunately the blocking in `acquire` happens at the implementation - /// layer of jobservers. On Unix this requires a blocking call to `read` - /// and on Windows this requires one of the `WaitFor*` functions. Both - /// of these situations aren't the easiest to deal with: - /// - /// * On Unix there's basically only one way to wake up a `read` early, and - /// that's through a signal. This is what the `make` implementation - /// itself uses, relying on `SIGCHLD` to wake up a blocking acquisition - /// of a new job token. Unfortunately nonblocking I/O is not an option - /// here, so it means that "waiting for one of two events" means that - /// the latter event must generate a signal! This is not always the case - /// on unix for all jobservers. - /// - /// * On Windows you'd have to basically use the `WaitForMultipleObjects` - /// which means that you've got to canonicalize all your event sources - /// into a `HANDLE` which also isn't the easiest thing to do - /// unfortunately. - /// - /// This function essentially attempts to ease these limitations by - /// converting this `Client` into a helper thread spawned into this - /// process. The application can then request that the helper thread - /// acquires tokens and the provided closure will be invoked for each token - /// acquired. - /// - /// The intention is that this function can be used to translate the event - /// of a token acquisition into an arbitrary user-defined event. - /// - /// # Arguments - /// - /// This function will consume the `Client` provided to be transferred to - /// the helper thread that is spawned. Additionally a closure `f` is - /// provided to be invoked whenever a token is acquired. - /// - /// This closure is only invoked after calls to - /// `HelperThread::request_token` have been made and a token itself has - /// been acquired. If an error happens while acquiring the token then - /// an error will be yielded to the closure as well. - /// - /// # Return Value - /// - /// This function will return an instance of the `HelperThread` structure - /// which is used to manage the helper thread associated with this client. - /// Through the `HelperThread` you'll request that tokens are acquired. - /// When acquired, the closure provided here is invoked. - /// - /// When the `HelperThread` structure is returned it will be gracefully - /// torn down, and the calling thread will be blocked until the thread is - /// torn down (which should be prompt). - /// - /// # Errors - /// - /// This function may fail due to creation of the helper thread or - /// auxiliary I/O objects to manage the helper thread. In any of these - /// situations the error is propagated upwards. - /// - /// # Platform-specific behavior - /// - /// On Windows this function behaves pretty normally as expected, but on - /// Unix the implementation is... a little heinous. As mentioned above - /// we're forced into blocking I/O for token acquisition, namely a blocking - /// call to `read`. We must be able to unblock this, however, to tear down - /// the helper thread gracefully! - /// - /// Essentially what happens is that we'll send a signal to the helper - /// thread spawned and rely on `EINTR` being returned to wake up the helper - /// thread. This involves installing a global `SIGUSR1` handler that does - /// nothing along with sending signals to that thread. This may cause - /// odd behavior in some applications, so it's recommended to review and - /// test thoroughly before using this. - pub fn into_helper_thread<F>(self, f: F) -> io::Result<HelperThread> - where - F: FnMut(io::Result<Acquired>) + Send + 'static, - { - let state = Arc::new(HelperState::default()); - Ok(HelperThread { - inner: Some(imp::spawn_helper(self, state.clone(), Box::new(f))?), - state, - }) - } - - /// Blocks the current thread until a token is acquired. - /// - /// This is the same as `acquire`, except that it doesn't return an RAII - /// helper. If successful the process will need to guarantee that - /// `release_raw` is called in the future. - pub fn acquire_raw(&self) -> io::Result<()> { - self.inner.acquire()?; - Ok(()) - } - - /// Releases a jobserver token back to the original jobserver. - /// - /// This is intended to be paired with `acquire_raw` if it was called, but - /// in some situations it could also be called to relinquish a process's - /// implicit token temporarily which is then re-acquired later. - pub fn release_raw(&self) -> io::Result<()> { - self.inner.release(None)?; - Ok(()) - } -} - -impl Drop for Acquired { - fn drop(&mut self) { - if !self.disabled { - drop(self.client.release(Some(&self.data))); - } - } -} - -/// Structure returned from `Client::into_helper_thread` to manage the lifetime -/// of the helper thread returned, see those associated docs for more info. -#[derive(Debug)] -pub struct HelperThread { - inner: Option<imp::Helper>, - state: Arc<HelperState>, -} - -impl HelperThread { - /// Request that the helper thread acquires a token, eventually calling the - /// original closure with a token when it's available. - /// - /// For more information, see the docs on that function. - pub fn request_token(&self) { - // Indicate that there's one more request for a token and then wake up - // the helper thread if it's sleeping. - self.state.lock().requests += 1; - self.state.cvar.notify_one(); - } -} - -impl Drop for HelperThread { - fn drop(&mut self) { - // Flag that the producer half is done so the helper thread should exit - // quickly if it's waiting. Wake it up if it's actually waiting - self.state.lock().producer_done = true; - self.state.cvar.notify_one(); - - // ... and afterwards perform any thread cleanup logic - self.inner.take().unwrap().join(); - } -} - -impl HelperState { - fn lock(&self) -> MutexGuard<'_, HelperInner> { - self.lock.lock().unwrap_or_else(|e| e.into_inner()) - } - - /// Executes `f` for each request for a token, where `f` is expected to - /// block and then provide the original closure with a token once it's - /// acquired. - /// - /// This is an infinite loop until the helper thread is dropped, at which - /// point everything should get interrupted. - fn for_each_request(&self, mut f: impl FnMut(&HelperState)) { - let mut lock = self.lock(); - - // We only execute while we could receive requests, but as soon as - // that's `false` we're out of here. - while !lock.producer_done { - // If no one's requested a token then we wait for someone to - // request a token. - if lock.requests == 0 { - lock = self.cvar.wait(lock).unwrap_or_else(|e| e.into_inner()); - continue; - } - - // Consume the request for a token, and then actually acquire a - // token after unlocking our lock (not that acquisition happens in - // `f`). This ensures that we don't actually hold the lock if we - // wait for a long time for a token. - lock.requests -= 1; - drop(lock); - f(self); - lock = self.lock(); - } - lock.consumer_done = true; - self.cvar.notify_one(); - } - - fn producer_done(&self) -> bool { - self.lock().producer_done - } -} - -#[test] -fn no_helper_deadlock() { - let x = crate::Client::new(32).unwrap(); - let _y = x.clone(); - std::mem::drop(x.into_helper_thread(|_| {}).unwrap()); -} +//! An implementation of the GNU make jobserver.
+//!
+//! This crate is an implementation, in Rust, of the GNU `make` jobserver for
+//! CLI tools that are interoperating with make or otherwise require some form
+//! of parallelism limiting across process boundaries. This was originally
+//! written for usage in Cargo to both (a) work when `cargo` is invoked from
+//! `make` (using `make`'s jobserver) and (b) work when `cargo` invokes build
+//! scripts, exporting a jobserver implementation for `make` processes to
+//! transitively use.
+//!
+//! The jobserver implementation can be found in [detail online][docs] but
+//! basically boils down to a cross-process semaphore. On Unix this is
+//! implemented with the `pipe` syscall and read/write ends of a pipe and on
+//! Windows this is implemented literally with IPC semaphores. Starting from
+//! GNU `make` version 4.4, named pipe becomes the default way in communication
+//! on Unix. This crate also supports that feature in the sense of inheriting
+//! and forwarding the correct environment.
+//!
+//! The jobserver protocol in `make` also dictates when tokens are acquired to
+//! run child work, and clients using this crate should take care to implement
+//! such details to ensure correct interoperation with `make` itself.
+//!
+//! ## Examples
+//!
+//! Connect to a jobserver that was set up by `make` or a different process:
+//!
+//! ```no_run
+//! use jobserver::Client;
+//!
+//! // See API documentation for why this is `unsafe`
+//! let client = match unsafe { Client::from_env() } {
+//! Some(client) => client,
+//! None => panic!("client not configured"),
+//! };
+//! ```
+//!
+//! Acquire and release token from a jobserver:
+//!
+//! ```no_run
+//! use jobserver::Client;
+//!
+//! let client = unsafe { Client::from_env().unwrap() };
+//! let token = client.acquire().unwrap(); // blocks until it is available
+//! drop(token); // releases the token when the work is done
+//! ```
+//!
+//! Create a new jobserver and configure a child process to have access:
+//!
+//! ```
+//! use std::process::Command;
+//! use jobserver::Client;
+//!
+//! let client = Client::new(4).expect("failed to create jobserver");
+//! let mut cmd = Command::new("make");
+//! client.configure(&mut cmd);
+//! ```
+//!
+//! ## Caveats
+//!
+//! This crate makes no attempt to release tokens back to a jobserver on
+//! abnormal exit of a process. If a process which acquires a token is killed
+//! with ctrl-c or some similar signal then tokens will not be released and the
+//! jobserver may be in a corrupt state.
+//!
+//! Note that this is typically ok as ctrl-c means that an entire build process
+//! is being torn down, but it's worth being aware of at least!
+//!
+//! ## Windows caveats
+//!
+//! There appear to be two implementations of `make` on Windows. On MSYS2 one
+//! typically comes as `mingw32-make` and the other as `make` itself. I'm not
+//! personally too familiar with what's going on here, but for jobserver-related
+//! information the `mingw32-make` implementation uses Windows semaphores
+//! whereas the `make` program does not. The `make` program appears to use file
+//! descriptors and I'm not really sure how it works, so this crate is not
+//! compatible with `make` on Windows. It is, however, compatible with
+//! `mingw32-make`.
+//!
+//! [docs]: http://make.mad-scientist.net/papers/jobserver-implementation/
+
+#![deny(missing_docs, missing_debug_implementations)]
+#![doc(html_root_url = "https://docs.rs/jobserver/0.1")]
+
+use std::env;
+use std::ffi::OsString;
+use std::io;
+use std::process::Command;
+use std::sync::{Arc, Condvar, Mutex, MutexGuard};
+
+mod error;
+#[cfg(unix)]
+#[path = "unix.rs"]
+mod imp;
+#[cfg(windows)]
+#[path = "windows.rs"]
+mod imp;
+#[cfg(not(any(unix, windows)))]
+#[path = "wasm.rs"]
+mod imp;
+
+/// A client of a jobserver
+///
+/// This structure is the main type exposed by this library, and is where
+/// interaction to a jobserver is configured through. Clients are either created
+/// from scratch in which case the internal semphore is initialied on the spot,
+/// or a client is created from the environment to connect to a jobserver
+/// already created.
+///
+/// Some usage examples can be found in the crate documentation for using a
+/// client.
+///
+/// Note that a `Client` implements the `Clone` trait, and all instances of a
+/// `Client` refer to the same jobserver instance.
+#[derive(Clone, Debug)]
+pub struct Client {
+ inner: Arc<imp::Client>,
+}
+
+/// An acquired token from a jobserver.
+///
+/// This token will be released back to the jobserver when it is dropped and
+/// otherwise represents the ability to spawn off another thread of work.
+#[derive(Debug)]
+pub struct Acquired {
+ client: Arc<imp::Client>,
+ data: imp::Acquired,
+ disabled: bool,
+}
+
+impl Acquired {
+ /// This drops the `Acquired` token without releasing the associated token.
+ ///
+ /// This is not generally useful, but can be helpful if you do not have the
+ /// ability to store an Acquired token but need to not yet release it.
+ ///
+ /// You'll typically want to follow this up with a call to `release_raw` or
+ /// similar to actually release the token later on.
+ pub fn drop_without_releasing(mut self) {
+ self.disabled = true;
+ }
+}
+
+#[derive(Default, Debug)]
+struct HelperState {
+ lock: Mutex<HelperInner>,
+ cvar: Condvar,
+}
+
+#[derive(Default, Debug)]
+struct HelperInner {
+ requests: usize,
+ producer_done: bool,
+ consumer_done: bool,
+}
+
+use error::FromEnvErrorInner;
+pub use error::{FromEnvError, FromEnvErrorKind};
+
+/// Return type for `from_env_ext` function.
+#[derive(Debug)]
+pub struct FromEnv {
+ /// Result of trying to get jobserver client from env.
+ pub client: Result<Client, FromEnvError>,
+ /// Name and value of the environment variable.
+ /// `None` if no relevant environment variable is found.
+ pub var: Option<(&'static str, OsString)>,
+}
+
+impl FromEnv {
+ fn new_ok(client: Client, var_name: &'static str, var_value: OsString) -> FromEnv {
+ FromEnv {
+ client: Ok(client),
+ var: Some((var_name, var_value)),
+ }
+ }
+ fn new_err(kind: FromEnvErrorInner, var_name: &'static str, var_value: OsString) -> FromEnv {
+ FromEnv {
+ client: Err(FromEnvError { inner: kind }),
+ var: Some((var_name, var_value)),
+ }
+ }
+}
+
+impl Client {
+ /// Creates a new jobserver initialized with the given parallelism limit.
+ ///
+ /// A client to the jobserver created will be returned. This client will
+ /// allow at most `limit` tokens to be acquired from it in parallel. More
+ /// calls to `acquire` will cause the calling thread to block.
+ ///
+ /// Note that the created `Client` is not automatically inherited into
+ /// spawned child processes from this program. Manual usage of the
+ /// `configure` function is required for a child process to have access to a
+ /// job server.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use jobserver::Client;
+ ///
+ /// let client = Client::new(4).expect("failed to create jobserver");
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if any I/O error happens when attempting to create the
+ /// jobserver client.
+ pub fn new(limit: usize) -> io::Result<Client> {
+ Ok(Client {
+ inner: Arc::new(imp::Client::new(limit)?),
+ })
+ }
+
+ /// Attempts to connect to the jobserver specified in this process's
+ /// environment.
+ ///
+ /// When the a `make` executable calls a child process it will configure the
+ /// environment of the child to ensure that it has handles to the jobserver
+ /// it's passing down. This function will attempt to look for these details
+ /// and connect to the jobserver.
+ ///
+ /// Note that the created `Client` is not automatically inherited into
+ /// spawned child processes from this program. Manual usage of the
+ /// `configure` function is required for a child process to have access to a
+ /// job server.
+ ///
+ /// # Return value
+ ///
+ /// `FromEnv` contains result and relevant environment variable.
+ /// If a jobserver was found in the environment and it looks correct then
+ /// result with the connected client will be returned. In other cases
+ /// result will contain `Err(FromEnvErr)`.
+ ///
+ /// Note that on Unix the `Client` returned **takes ownership of the file
+ /// descriptors specified in the environment**. Jobservers on Unix are
+ /// implemented with `pipe` file descriptors, and they're inherited from
+ /// parent processes. This `Client` returned takes ownership of the file
+ /// descriptors for this process and will close the file descriptors after
+ /// this value is dropped.
+ ///
+ /// Additionally on Unix this function will configure the file descriptors
+ /// with `CLOEXEC` so they're not automatically inherited by spawned
+ /// children.
+ ///
+ /// On unix if `check_pipe` enabled this function will check if provided
+ /// files are actually pipes.
+ ///
+ /// # Safety
+ ///
+ /// This function is `unsafe` to call on Unix specifically as it
+ /// transitively requires usage of the `from_raw_fd` function, which is
+ /// itself unsafe in some circumstances.
+ ///
+ /// It's recommended to call this function very early in the lifetime of a
+ /// program before any other file descriptors are opened. That way you can
+ /// make sure to take ownership properly of the file descriptors passed
+ /// down, if any.
+ ///
+ /// It's generally unsafe to call this function twice in a program if the
+ /// previous invocation returned `Some`.
+ ///
+ /// Note, though, that on Windows it should be safe to call this function
+ /// any number of times.
+ pub unsafe fn from_env_ext(check_pipe: bool) -> FromEnv {
+ let (env, var_os) = match ["CARGO_MAKEFLAGS", "MAKEFLAGS", "MFLAGS"]
+ .iter()
+ .map(|&env| env::var_os(env).map(|var| (env, var)))
+ .find_map(|p| p)
+ {
+ Some((env, var_os)) => (env, var_os),
+ None => return FromEnv::new_err(FromEnvErrorInner::NoEnvVar, "", Default::default()),
+ };
+
+ let var = match var_os.to_str() {
+ Some(var) => var,
+ None => {
+ let err = FromEnvErrorInner::CannotParse("not valid UTF-8".to_string());
+ return FromEnv::new_err(err, env, var_os);
+ }
+ };
+
+ let (arg, pos) = match ["--jobserver-fds=", "--jobserver-auth="]
+ .iter()
+ .map(|&arg| var.find(arg).map(|pos| (arg, pos)))
+ .find_map(|pos| pos)
+ {
+ Some((arg, pos)) => (arg, pos),
+ None => return FromEnv::new_err(FromEnvErrorInner::NoJobserver, env, var_os),
+ };
+
+ let s = var[pos + arg.len()..].split(' ').next().unwrap();
+ match imp::Client::open(s, check_pipe) {
+ Ok(c) => FromEnv::new_ok(Client { inner: Arc::new(c) }, env, var_os),
+ Err(err) => FromEnv::new_err(err, env, var_os),
+ }
+ }
+
+ /// Attempts to connect to the jobserver specified in this process's
+ /// environment.
+ ///
+ /// Wraps `from_env_ext` and discards error details.
+ pub unsafe fn from_env() -> Option<Client> {
+ Self::from_env_ext(false).client.ok()
+ }
+
+ /// Acquires a token from this jobserver client.
+ ///
+ /// This function will block the calling thread until a new token can be
+ /// acquired from the jobserver.
+ ///
+ /// # Return value
+ ///
+ /// On successful acquisition of a token an instance of `Acquired` is
+ /// returned. This structure, when dropped, will release the token back to
+ /// the jobserver. It's recommended to avoid leaking this value.
+ ///
+ /// # Errors
+ ///
+ /// If an I/O error happens while acquiring a token then this function will
+ /// return immediately with the error. If an error is returned then a token
+ /// was not acquired.
+ pub fn acquire(&self) -> io::Result<Acquired> {
+ let data = self.inner.acquire()?;
+ Ok(Acquired {
+ client: self.inner.clone(),
+ data,
+ disabled: false,
+ })
+ }
+
+ /// Returns amount of tokens in the read-side pipe.
+ ///
+ /// # Return value
+ ///
+ /// Number of bytes available to be read from the jobserver pipe
+ ///
+ /// # Errors
+ ///
+ /// Underlying errors from the ioctl will be passed up.
+ pub fn available(&self) -> io::Result<usize> {
+ self.inner.available()
+ }
+
+ /// Configures a child process to have access to this client's jobserver as
+ /// well.
+ ///
+ /// This function is required to be called to ensure that a jobserver is
+ /// properly inherited to a child process. If this function is *not* called
+ /// then this `Client` will not be accessible in the child process. In other
+ /// words, if not called, then `Client::from_env` will return `None` in the
+ /// child process (or the equivalent of `Child::from_env` that `make` uses).
+ ///
+ /// ## Platform-specific behavior
+ ///
+ /// On Unix and Windows this will clobber the `CARGO_MAKEFLAGS` environment
+ /// variables for the child process, and on Unix this will also allow the
+ /// two file descriptors for this client to be inherited to the child.
+ ///
+ /// On platforms other than Unix and Windows this panics.
+ pub fn configure(&self, cmd: &mut Command) {
+ cmd.env("CARGO_MAKEFLAGS", &self.mflags_env());
+ self.inner.configure(cmd);
+ }
+
+ /// Configures a child process to have access to this client's jobserver as
+ /// well.
+ ///
+ /// This function is required to be called to ensure that a jobserver is
+ /// properly inherited to a child process. If this function is *not* called
+ /// then this `Client` will not be accessible in the child process. In other
+ /// words, if not called, then `Client::from_env` will return `None` in the
+ /// child process (or the equivalent of `Child::from_env` that `make` uses).
+ ///
+ /// ## Platform-specific behavior
+ ///
+ /// On Unix and Windows this will clobber the `CARGO_MAKEFLAGS`,
+ /// `MAKEFLAGS` and `MFLAGS` environment variables for the child process,
+ /// and on Unix this will also allow the two file descriptors for
+ /// this client to be inherited to the child.
+ ///
+ /// On platforms other than Unix and Windows this panics.
+ pub fn configure_make(&self, cmd: &mut Command) {
+ let value = self.mflags_env();
+ cmd.env("CARGO_MAKEFLAGS", &value);
+ cmd.env("MAKEFLAGS", &value);
+ cmd.env("MFLAGS", &value);
+ self.inner.configure(cmd);
+ }
+
+ fn mflags_env(&self) -> String {
+ let arg = self.inner.string_arg();
+ // Older implementations of make use `--jobserver-fds` and newer
+ // implementations use `--jobserver-auth`, pass both to try to catch
+ // both implementations.
+ format!("-j --jobserver-fds={0} --jobserver-auth={0}", arg)
+ }
+
+ /// Converts this `Client` into a helper thread to deal with a blocking
+ /// `acquire` function a little more easily.
+ ///
+ /// The fact that the `acquire` function on `Client` blocks isn't always
+ /// the easiest to work with. Typically you're using a jobserver to
+ /// manage running other events in parallel! This means that you need to
+ /// either (a) wait for an existing job to finish or (b) wait for a
+ /// new token to become available.
+ ///
+ /// Unfortunately the blocking in `acquire` happens at the implementation
+ /// layer of jobservers. On Unix this requires a blocking call to `read`
+ /// and on Windows this requires one of the `WaitFor*` functions. Both
+ /// of these situations aren't the easiest to deal with:
+ ///
+ /// * On Unix there's basically only one way to wake up a `read` early, and
+ /// that's through a signal. This is what the `make` implementation
+ /// itself uses, relying on `SIGCHLD` to wake up a blocking acquisition
+ /// of a new job token. Unfortunately nonblocking I/O is not an option
+ /// here, so it means that "waiting for one of two events" means that
+ /// the latter event must generate a signal! This is not always the case
+ /// on unix for all jobservers.
+ ///
+ /// * On Windows you'd have to basically use the `WaitForMultipleObjects`
+ /// which means that you've got to canonicalize all your event sources
+ /// into a `HANDLE` which also isn't the easiest thing to do
+ /// unfortunately.
+ ///
+ /// This function essentially attempts to ease these limitations by
+ /// converting this `Client` into a helper thread spawned into this
+ /// process. The application can then request that the helper thread
+ /// acquires tokens and the provided closure will be invoked for each token
+ /// acquired.
+ ///
+ /// The intention is that this function can be used to translate the event
+ /// of a token acquisition into an arbitrary user-defined event.
+ ///
+ /// # Arguments
+ ///
+ /// This function will consume the `Client` provided to be transferred to
+ /// the helper thread that is spawned. Additionally a closure `f` is
+ /// provided to be invoked whenever a token is acquired.
+ ///
+ /// This closure is only invoked after calls to
+ /// `HelperThread::request_token` have been made and a token itself has
+ /// been acquired. If an error happens while acquiring the token then
+ /// an error will be yielded to the closure as well.
+ ///
+ /// # Return Value
+ ///
+ /// This function will return an instance of the `HelperThread` structure
+ /// which is used to manage the helper thread associated with this client.
+ /// Through the `HelperThread` you'll request that tokens are acquired.
+ /// When acquired, the closure provided here is invoked.
+ ///
+ /// When the `HelperThread` structure is returned it will be gracefully
+ /// torn down, and the calling thread will be blocked until the thread is
+ /// torn down (which should be prompt).
+ ///
+ /// # Errors
+ ///
+ /// This function may fail due to creation of the helper thread or
+ /// auxiliary I/O objects to manage the helper thread. In any of these
+ /// situations the error is propagated upwards.
+ ///
+ /// # Platform-specific behavior
+ ///
+ /// On Windows this function behaves pretty normally as expected, but on
+ /// Unix the implementation is... a little heinous. As mentioned above
+ /// we're forced into blocking I/O for token acquisition, namely a blocking
+ /// call to `read`. We must be able to unblock this, however, to tear down
+ /// the helper thread gracefully!
+ ///
+ /// Essentially what happens is that we'll send a signal to the helper
+ /// thread spawned and rely on `EINTR` being returned to wake up the helper
+ /// thread. This involves installing a global `SIGUSR1` handler that does
+ /// nothing along with sending signals to that thread. This may cause
+ /// odd behavior in some applications, so it's recommended to review and
+ /// test thoroughly before using this.
+ pub fn into_helper_thread<F>(self, f: F) -> io::Result<HelperThread>
+ where
+ F: FnMut(io::Result<Acquired>) + Send + 'static,
+ {
+ let state = Arc::new(HelperState::default());
+ Ok(HelperThread {
+ inner: Some(imp::spawn_helper(self, state.clone(), Box::new(f))?),
+ state,
+ })
+ }
+
+ /// Blocks the current thread until a token is acquired.
+ ///
+ /// This is the same as `acquire`, except that it doesn't return an RAII
+ /// helper. If successful the process will need to guarantee that
+ /// `release_raw` is called in the future.
+ pub fn acquire_raw(&self) -> io::Result<()> {
+ self.inner.acquire()?;
+ Ok(())
+ }
+
+ /// Releases a jobserver token back to the original jobserver.
+ ///
+ /// This is intended to be paired with `acquire_raw` if it was called, but
+ /// in some situations it could also be called to relinquish a process's
+ /// implicit token temporarily which is then re-acquired later.
+ pub fn release_raw(&self) -> io::Result<()> {
+ self.inner.release(None)?;
+ Ok(())
+ }
+}
+
+impl Drop for Acquired {
+ fn drop(&mut self) {
+ if !self.disabled {
+ drop(self.client.release(Some(&self.data)));
+ }
+ }
+}
+
+/// Structure returned from `Client::into_helper_thread` to manage the lifetime
+/// of the helper thread returned, see those associated docs for more info.
+#[derive(Debug)]
+pub struct HelperThread {
+ inner: Option<imp::Helper>,
+ state: Arc<HelperState>,
+}
+
+impl HelperThread {
+ /// Request that the helper thread acquires a token, eventually calling the
+ /// original closure with a token when it's available.
+ ///
+ /// For more information, see the docs on that function.
+ pub fn request_token(&self) {
+ // Indicate that there's one more request for a token and then wake up
+ // the helper thread if it's sleeping.
+ self.state.lock().requests += 1;
+ self.state.cvar.notify_one();
+ }
+}
+
+impl Drop for HelperThread {
+ fn drop(&mut self) {
+ // Flag that the producer half is done so the helper thread should exit
+ // quickly if it's waiting. Wake it up if it's actually waiting
+ self.state.lock().producer_done = true;
+ self.state.cvar.notify_one();
+
+ // ... and afterwards perform any thread cleanup logic
+ self.inner.take().unwrap().join();
+ }
+}
+
+impl HelperState {
+ fn lock(&self) -> MutexGuard<'_, HelperInner> {
+ self.lock.lock().unwrap_or_else(|e| e.into_inner())
+ }
+
+ /// Executes `f` for each request for a token, where `f` is expected to
+ /// block and then provide the original closure with a token once it's
+ /// acquired.
+ ///
+ /// This is an infinite loop until the helper thread is dropped, at which
+ /// point everything should get interrupted.
+ fn for_each_request(&self, mut f: impl FnMut(&HelperState)) {
+ let mut lock = self.lock();
+
+ // We only execute while we could receive requests, but as soon as
+ // that's `false` we're out of here.
+ while !lock.producer_done {
+ // If no one's requested a token then we wait for someone to
+ // request a token.
+ if lock.requests == 0 {
+ lock = self.cvar.wait(lock).unwrap_or_else(|e| e.into_inner());
+ continue;
+ }
+
+ // Consume the request for a token, and then actually acquire a
+ // token after unlocking our lock (not that acquisition happens in
+ // `f`). This ensures that we don't actually hold the lock if we
+ // wait for a long time for a token.
+ lock.requests -= 1;
+ drop(lock);
+ f(self);
+ lock = self.lock();
+ }
+ lock.consumer_done = true;
+ self.cvar.notify_one();
+ }
+
+ fn producer_done(&self) -> bool {
+ self.lock().producer_done
+ }
+}
+
+#[test]
+fn no_helper_deadlock() {
+ let x = crate::Client::new(32).unwrap();
+ let _y = x.clone();
+ std::mem::drop(x.into_helper_thread(|_| {}).unwrap());
+}
diff --git a/vendor/jobserver/src/unix.rs b/vendor/jobserver/src/unix.rs index e4b143505..b2312b08f 100644 --- a/vendor/jobserver/src/unix.rs +++ b/vendor/jobserver/src/unix.rs @@ -1,431 +1,477 @@ -use libc::c_int; - -use std::fs::{File, OpenOptions}; -use std::io::{self, Read, Write}; -use std::mem; -use std::mem::MaybeUninit; -use std::os::unix::prelude::*; -use std::path::{Path, PathBuf}; -use std::process::Command; -use std::ptr; -use std::sync::{Arc, Once}; -use std::thread::{self, Builder, JoinHandle}; -use std::time::Duration; - -#[derive(Debug)] -pub enum Client { - /// `--jobserver-auth=R,W` - Pipe { read: File, write: File }, - /// `--jobserver-auth=fifo:PATH` - Fifo { file: File, path: PathBuf }, -} - -#[derive(Debug)] -pub struct Acquired { - byte: u8, -} - -impl Client { - pub fn new(mut limit: usize) -> io::Result<Client> { - let client = unsafe { Client::mk()? }; - - // I don't think the character written here matters, but I could be - // wrong! - const BUFFER: [u8; 128] = [b'|'; 128]; - - let mut write = client.write(); - - set_nonblocking(write.as_raw_fd(), true)?; - - while limit > 0 { - let n = limit.min(BUFFER.len()); - - write.write_all(&BUFFER[..n])?; - limit -= n; - } - - set_nonblocking(write.as_raw_fd(), false)?; - - Ok(client) - } - - unsafe fn mk() -> io::Result<Client> { - let mut pipes = [0; 2]; - - // Attempt atomically-create-with-cloexec if we can on Linux, - // detected by using the `syscall` function in `libc` to try to work - // with as many kernels/glibc implementations as possible. - #[cfg(target_os = "linux")] - { - use std::sync::atomic::{AtomicBool, Ordering}; - - static PIPE2_AVAILABLE: AtomicBool = AtomicBool::new(true); - if PIPE2_AVAILABLE.load(Ordering::SeqCst) { - match libc::syscall(libc::SYS_pipe2, pipes.as_mut_ptr(), libc::O_CLOEXEC) { - -1 => { - let err = io::Error::last_os_error(); - if err.raw_os_error() == Some(libc::ENOSYS) { - PIPE2_AVAILABLE.store(false, Ordering::SeqCst); - } else { - return Err(err); - } - } - _ => return Ok(Client::from_fds(pipes[0], pipes[1])), - } - } - } - - cvt(libc::pipe(pipes.as_mut_ptr()))?; - drop(set_cloexec(pipes[0], true)); - drop(set_cloexec(pipes[1], true)); - Ok(Client::from_fds(pipes[0], pipes[1])) - } - - pub unsafe fn open(s: &str) -> Option<Client> { - Client::from_fifo(s).or_else(|| Client::from_pipe(s)) - } - - /// `--jobserver-auth=fifo:PATH` - fn from_fifo(s: &str) -> Option<Client> { - let mut parts = s.splitn(2, ':'); - if parts.next().unwrap() != "fifo" { - return None; - } - let path = match parts.next() { - Some(p) => Path::new(p), - None => return None, - }; - let file = match OpenOptions::new().read(true).write(true).open(path) { - Ok(f) => f, - Err(_) => return None, - }; - Some(Client::Fifo { - file, - path: path.into(), - }) - } - - /// `--jobserver-auth=R,W` - unsafe fn from_pipe(s: &str) -> Option<Client> { - let mut parts = s.splitn(2, ','); - let read = parts.next().unwrap(); - let write = match parts.next() { - Some(s) => s, - None => return None, - }; - - let read = match read.parse() { - Ok(n) => n, - Err(_) => return None, - }; - let write = match write.parse() { - Ok(n) => n, - Err(_) => return None, - }; - - // Ok so we've got two integers that look like file descriptors, but - // for extra sanity checking let's see if they actually look like - // instances of a pipe before we return the client. - // - // If we're called from `make` *without* the leading + on our rule - // then we'll have `MAKEFLAGS` env vars but won't actually have - // access to the file descriptors. - if is_valid_fd(read) && is_valid_fd(write) { - drop(set_cloexec(read, true)); - drop(set_cloexec(write, true)); - Some(Client::from_fds(read, write)) - } else { - None - } - } - - unsafe fn from_fds(read: c_int, write: c_int) -> Client { - Client::Pipe { - read: File::from_raw_fd(read), - write: File::from_raw_fd(write), - } - } - - /// Gets the read end of our jobserver client. - fn read(&self) -> &File { - match self { - Client::Pipe { read, .. } => read, - Client::Fifo { file, .. } => file, - } - } - - /// Gets the write end of our jobserver client. - fn write(&self) -> &File { - match self { - Client::Pipe { write, .. } => write, - Client::Fifo { file, .. } => file, - } - } - - pub fn acquire(&self) -> io::Result<Acquired> { - // Ignore interrupts and keep trying if that happens - loop { - if let Some(token) = self.acquire_allow_interrupts()? { - return Ok(token); - } - } - } - - /// Block waiting for a token, returning `None` if we're interrupted with - /// EINTR. - fn acquire_allow_interrupts(&self) -> io::Result<Option<Acquired>> { - // We don't actually know if the file descriptor here is set in - // blocking or nonblocking mode. AFAIK all released versions of - // `make` use blocking fds for the jobserver, but the unreleased - // version of `make` doesn't. In the unreleased version jobserver - // fds are set to nonblocking and combined with `pselect` - // internally. - // - // Here we try to be compatible with both strategies. We optimistically - // try to read from the file descriptor which then may block, return - // a token or indicate that polling is needed. - // Blocking reads (if possible) allows the kernel to be more selective - // about which readers to wake up when a token is written to the pipe. - // - // We use `poll` here to block this thread waiting for read - // readiness, and then afterwards we perform the `read` itself. If - // the `read` returns that it would block then we start over and try - // again. - // - // Also note that we explicitly don't handle EINTR here. That's used - // to shut us down, so we otherwise punt all errors upwards. - unsafe { - let mut fd: libc::pollfd = mem::zeroed(); - let mut read = self.read(); - fd.fd = read.as_raw_fd(); - fd.events = libc::POLLIN; - loop { - let mut buf = [0]; - match read.read(&mut buf) { - Ok(1) => return Ok(Some(Acquired { byte: buf[0] })), - Ok(_) => { - return Err(io::Error::new( - io::ErrorKind::Other, - "early EOF on jobserver pipe", - )) - } - Err(e) => match e.kind() { - io::ErrorKind::WouldBlock => { /* fall through to polling */ } - io::ErrorKind::Interrupted => return Ok(None), - _ => return Err(e), - }, - } - - loop { - fd.revents = 0; - if libc::poll(&mut fd, 1, -1) == -1 { - let e = io::Error::last_os_error(); - return match e.kind() { - io::ErrorKind::Interrupted => Ok(None), - _ => Err(e), - }; - } - if fd.revents != 0 { - break; - } - } - } - } - } - - pub fn release(&self, data: Option<&Acquired>) -> io::Result<()> { - // Note that the fd may be nonblocking but we're going to go ahead - // and assume that the writes here are always nonblocking (we can - // always quickly release a token). If that turns out to not be the - // case we'll get an error anyway! - let byte = data.map(|d| d.byte).unwrap_or(b'+'); - match self.write().write(&[byte])? { - 1 => Ok(()), - _ => Err(io::Error::new( - io::ErrorKind::Other, - "failed to write token back to jobserver", - )), - } - } - - pub fn string_arg(&self) -> String { - match self { - Client::Pipe { read, write } => format!("{},{}", read.as_raw_fd(), write.as_raw_fd()), - Client::Fifo { path, .. } => format!("fifo:{}", path.to_str().unwrap()), - } - } - - pub fn available(&self) -> io::Result<usize> { - let mut len = MaybeUninit::<c_int>::uninit(); - cvt(unsafe { libc::ioctl(self.read().as_raw_fd(), libc::FIONREAD, len.as_mut_ptr()) })?; - Ok(unsafe { len.assume_init() } as usize) - } - - pub fn configure(&self, cmd: &mut Command) { - match self { - // We `File::open`ed it when inheriting from environment, - // so no need to set cloexec for fifo. - Client::Fifo { .. } => return, - Client::Pipe { .. } => {} - }; - // Here we basically just want to say that in the child process - // we'll configure the read/write file descriptors to *not* be - // cloexec, so they're inherited across the exec and specified as - // integers through `string_arg` above. - let read = self.read().as_raw_fd(); - let write = self.write().as_raw_fd(); - unsafe { - cmd.pre_exec(move || { - set_cloexec(read, false)?; - set_cloexec(write, false)?; - Ok(()) - }); - } - } -} - -#[derive(Debug)] -pub struct Helper { - thread: JoinHandle<()>, - state: Arc<super::HelperState>, -} - -pub(crate) fn spawn_helper( - client: crate::Client, - state: Arc<super::HelperState>, - mut f: Box<dyn FnMut(io::Result<crate::Acquired>) + Send>, -) -> io::Result<Helper> { - static USR1_INIT: Once = Once::new(); - let mut err = None; - USR1_INIT.call_once(|| unsafe { - let mut new: libc::sigaction = mem::zeroed(); - #[cfg(target_os = "aix")] - { - new.sa_union.__su_sigaction = sigusr1_handler; - } - #[cfg(not(target_os = "aix"))] - { - new.sa_sigaction = sigusr1_handler as usize; - } - new.sa_flags = libc::SA_SIGINFO as _; - if libc::sigaction(libc::SIGUSR1, &new, ptr::null_mut()) != 0 { - err = Some(io::Error::last_os_error()); - } - }); - - if let Some(e) = err.take() { - return Err(e); - } - - let state2 = state.clone(); - let thread = Builder::new().spawn(move || { - state2.for_each_request(|helper| loop { - match client.inner.acquire_allow_interrupts() { - Ok(Some(data)) => { - break f(Ok(crate::Acquired { - client: client.inner.clone(), - data, - disabled: false, - })) - } - Err(e) => break f(Err(e)), - Ok(None) if helper.producer_done() => break, - Ok(None) => {} - } - }); - })?; - - Ok(Helper { thread, state }) -} - -impl Helper { - pub fn join(self) { - let dur = Duration::from_millis(10); - let mut state = self.state.lock(); - debug_assert!(state.producer_done); - - // We need to join our helper thread, and it could be blocked in one - // of two locations. First is the wait for a request, but the - // initial drop of `HelperState` will take care of that. Otherwise - // it may be blocked in `client.acquire()`. We actually have no way - // of interrupting that, so resort to `pthread_kill` as a fallback. - // This signal should interrupt any blocking `read` call with - // `io::ErrorKind::Interrupt` and cause the thread to cleanly exit. - // - // Note that we don't do this forever though since there's a chance - // of bugs, so only do this opportunistically to make a best effort - // at clearing ourselves up. - for _ in 0..100 { - if state.consumer_done { - break; - } - unsafe { - // Ignore the return value here of `pthread_kill`, - // apparently on OSX if you kill a dead thread it will - // return an error, but on other platforms it may not. In - // that sense we don't actually know if this will succeed or - // not! - libc::pthread_kill(self.thread.as_pthread_t() as _, libc::SIGUSR1); - } - state = self - .state - .cvar - .wait_timeout(state, dur) - .unwrap_or_else(|e| e.into_inner()) - .0; - thread::yield_now(); // we really want the other thread to run - } - - // If we managed to actually see the consumer get done, then we can - // definitely wait for the thread. Otherwise it's... off in the ether - // I guess? - if state.consumer_done { - drop(self.thread.join()); - } - } -} - -fn is_valid_fd(fd: c_int) -> bool { - unsafe { libc::fcntl(fd, libc::F_GETFD) != -1 } -} - -fn set_cloexec(fd: c_int, set: bool) -> io::Result<()> { - unsafe { - let previous = cvt(libc::fcntl(fd, libc::F_GETFD))?; - let new = if set { - previous | libc::FD_CLOEXEC - } else { - previous & !libc::FD_CLOEXEC - }; - if new != previous { - cvt(libc::fcntl(fd, libc::F_SETFD, new))?; - } - Ok(()) - } -} - -fn set_nonblocking(fd: c_int, set: bool) -> io::Result<()> { - let status_flag = if set { libc::O_NONBLOCK } else { 0 }; - - unsafe { - cvt(libc::fcntl(fd, libc::F_SETFL, status_flag))?; - } - - Ok(()) -} - -fn cvt(t: c_int) -> io::Result<c_int> { - if t == -1 { - Err(io::Error::last_os_error()) - } else { - Ok(t) - } -} - -extern "C" fn sigusr1_handler( - _signum: c_int, - _info: *mut libc::siginfo_t, - _ptr: *mut libc::c_void, -) { - // nothing to do -} +use libc::c_int;
+
+use crate::FromEnvErrorInner;
+use std::fs::{File, OpenOptions};
+use std::io::{self, Read, Write};
+use std::mem;
+use std::mem::MaybeUninit;
+use std::os::unix::prelude::*;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+use std::ptr;
+use std::sync::{Arc, Once};
+use std::thread::{self, Builder, JoinHandle};
+use std::time::Duration;
+
+#[derive(Debug)]
+pub enum Client {
+ /// `--jobserver-auth=R,W`
+ Pipe { read: File, write: File },
+ /// `--jobserver-auth=fifo:PATH`
+ Fifo { file: File, path: PathBuf },
+}
+
+#[derive(Debug)]
+pub struct Acquired {
+ byte: u8,
+}
+
+impl Client {
+ pub fn new(mut limit: usize) -> io::Result<Client> {
+ let client = unsafe { Client::mk()? };
+
+ // I don't think the character written here matters, but I could be
+ // wrong!
+ const BUFFER: [u8; 128] = [b'|'; 128];
+
+ let mut write = client.write();
+
+ set_nonblocking(write.as_raw_fd(), true)?;
+
+ while limit > 0 {
+ let n = limit.min(BUFFER.len());
+
+ write.write_all(&BUFFER[..n])?;
+ limit -= n;
+ }
+
+ set_nonblocking(write.as_raw_fd(), false)?;
+
+ Ok(client)
+ }
+
+ unsafe fn mk() -> io::Result<Client> {
+ let mut pipes = [0; 2];
+
+ // Attempt atomically-create-with-cloexec if we can on Linux,
+ // detected by using the `syscall` function in `libc` to try to work
+ // with as many kernels/glibc implementations as possible.
+ #[cfg(target_os = "linux")]
+ {
+ use std::sync::atomic::{AtomicBool, Ordering};
+
+ static PIPE2_AVAILABLE: AtomicBool = AtomicBool::new(true);
+ if PIPE2_AVAILABLE.load(Ordering::SeqCst) {
+ match libc::syscall(libc::SYS_pipe2, pipes.as_mut_ptr(), libc::O_CLOEXEC) {
+ -1 => {
+ let err = io::Error::last_os_error();
+ if err.raw_os_error() == Some(libc::ENOSYS) {
+ PIPE2_AVAILABLE.store(false, Ordering::SeqCst);
+ } else {
+ return Err(err);
+ }
+ }
+ _ => return Ok(Client::from_fds(pipes[0], pipes[1])),
+ }
+ }
+ }
+
+ cvt(libc::pipe(pipes.as_mut_ptr()))?;
+ drop(set_cloexec(pipes[0], true));
+ drop(set_cloexec(pipes[1], true));
+ Ok(Client::from_fds(pipes[0], pipes[1]))
+ }
+
+ pub(crate) unsafe fn open(s: &str, check_pipe: bool) -> Result<Client, FromEnvErrorInner> {
+ if let Some(client) = Self::from_fifo(s)? {
+ return Ok(client);
+ }
+ if let Some(client) = Self::from_pipe(s, check_pipe)? {
+ return Ok(client);
+ }
+ Err(FromEnvErrorInner::CannotParse(format!(
+ "expected `fifo:PATH` or `R,W`, found `{s}`"
+ )))
+ }
+
+ /// `--jobserver-auth=fifo:PATH`
+ fn from_fifo(s: &str) -> Result<Option<Client>, FromEnvErrorInner> {
+ let mut parts = s.splitn(2, ':');
+ if parts.next().unwrap() != "fifo" {
+ return Ok(None);
+ }
+ let path_str = parts.next().ok_or_else(|| {
+ FromEnvErrorInner::CannotParse("expected a path after `fifo:`".to_string())
+ })?;
+ let path = Path::new(path_str);
+ let file = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(path)
+ .map_err(|err| FromEnvErrorInner::CannotOpenPath(path_str.to_string(), err))?;
+ Ok(Some(Client::Fifo {
+ file,
+ path: path.into(),
+ }))
+ }
+
+ /// `--jobserver-auth=R,W`
+ unsafe fn from_pipe(s: &str, check_pipe: bool) -> Result<Option<Client>, FromEnvErrorInner> {
+ let mut parts = s.splitn(2, ',');
+ let read = parts.next().unwrap();
+ let write = match parts.next() {
+ Some(w) => w,
+ None => return Ok(None),
+ };
+ let read = read
+ .parse()
+ .map_err(|e| FromEnvErrorInner::CannotParse(format!("cannot parse `read` fd: {e}")))?;
+ let write = write
+ .parse()
+ .map_err(|e| FromEnvErrorInner::CannotParse(format!("cannot parse `write` fd: {e}")))?;
+
+ // Ok so we've got two integers that look like file descriptors, but
+ // for extra sanity checking let's see if they actually look like
+ // valid files and instances of a pipe if feature enabled before we
+ // return the client.
+ //
+ // If we're called from `make` *without* the leading + on our rule
+ // then we'll have `MAKEFLAGS` env vars but won't actually have
+ // access to the file descriptors.
+ //
+ // `NotAPipe` is a worse error, return it if it's reported for any of the two fds.
+ match (fd_check(read, check_pipe), fd_check(write, check_pipe)) {
+ (read_err @ Err(FromEnvErrorInner::NotAPipe(..)), _) => read_err?,
+ (_, write_err @ Err(FromEnvErrorInner::NotAPipe(..))) => write_err?,
+ (read_err, write_err) => {
+ read_err?;
+ write_err?;
+ }
+ }
+
+ drop(set_cloexec(read, true));
+ drop(set_cloexec(write, true));
+ Ok(Some(Client::from_fds(read, write)))
+ }
+
+ unsafe fn from_fds(read: c_int, write: c_int) -> Client {
+ Client::Pipe {
+ read: File::from_raw_fd(read),
+ write: File::from_raw_fd(write),
+ }
+ }
+
+ /// Gets the read end of our jobserver client.
+ fn read(&self) -> &File {
+ match self {
+ Client::Pipe { read, .. } => read,
+ Client::Fifo { file, .. } => file,
+ }
+ }
+
+ /// Gets the write end of our jobserver client.
+ fn write(&self) -> &File {
+ match self {
+ Client::Pipe { write, .. } => write,
+ Client::Fifo { file, .. } => file,
+ }
+ }
+
+ pub fn acquire(&self) -> io::Result<Acquired> {
+ // Ignore interrupts and keep trying if that happens
+ loop {
+ if let Some(token) = self.acquire_allow_interrupts()? {
+ return Ok(token);
+ }
+ }
+ }
+
+ /// Block waiting for a token, returning `None` if we're interrupted with
+ /// EINTR.
+ fn acquire_allow_interrupts(&self) -> io::Result<Option<Acquired>> {
+ // We don't actually know if the file descriptor here is set in
+ // blocking or nonblocking mode. AFAIK all released versions of
+ // `make` use blocking fds for the jobserver, but the unreleased
+ // version of `make` doesn't. In the unreleased version jobserver
+ // fds are set to nonblocking and combined with `pselect`
+ // internally.
+ //
+ // Here we try to be compatible with both strategies. We optimistically
+ // try to read from the file descriptor which then may block, return
+ // a token or indicate that polling is needed.
+ // Blocking reads (if possible) allows the kernel to be more selective
+ // about which readers to wake up when a token is written to the pipe.
+ //
+ // We use `poll` here to block this thread waiting for read
+ // readiness, and then afterwards we perform the `read` itself. If
+ // the `read` returns that it would block then we start over and try
+ // again.
+ //
+ // Also note that we explicitly don't handle EINTR here. That's used
+ // to shut us down, so we otherwise punt all errors upwards.
+ unsafe {
+ let mut fd: libc::pollfd = mem::zeroed();
+ let mut read = self.read();
+ fd.fd = read.as_raw_fd();
+ fd.events = libc::POLLIN;
+ loop {
+ let mut buf = [0];
+ match read.read(&mut buf) {
+ Ok(1) => return Ok(Some(Acquired { byte: buf[0] })),
+ Ok(_) => {
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ "early EOF on jobserver pipe",
+ ));
+ }
+ Err(e) => match e.kind() {
+ io::ErrorKind::WouldBlock => { /* fall through to polling */ }
+ io::ErrorKind::Interrupted => return Ok(None),
+ _ => return Err(e),
+ },
+ }
+
+ loop {
+ fd.revents = 0;
+ if libc::poll(&mut fd, 1, -1) == -1 {
+ let e = io::Error::last_os_error();
+ return match e.kind() {
+ io::ErrorKind::Interrupted => Ok(None),
+ _ => Err(e),
+ };
+ }
+ if fd.revents != 0 {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ pub fn release(&self, data: Option<&Acquired>) -> io::Result<()> {
+ // Note that the fd may be nonblocking but we're going to go ahead
+ // and assume that the writes here are always nonblocking (we can
+ // always quickly release a token). If that turns out to not be the
+ // case we'll get an error anyway!
+ let byte = data.map(|d| d.byte).unwrap_or(b'+');
+ match self.write().write(&[byte])? {
+ 1 => Ok(()),
+ _ => Err(io::Error::new(
+ io::ErrorKind::Other,
+ "failed to write token back to jobserver",
+ )),
+ }
+ }
+
+ pub fn string_arg(&self) -> String {
+ match self {
+ Client::Pipe { read, write } => format!("{},{}", read.as_raw_fd(), write.as_raw_fd()),
+ Client::Fifo { path, .. } => format!("fifo:{}", path.to_str().unwrap()),
+ }
+ }
+
+ pub fn available(&self) -> io::Result<usize> {
+ let mut len = MaybeUninit::<c_int>::uninit();
+ cvt(unsafe { libc::ioctl(self.read().as_raw_fd(), libc::FIONREAD, len.as_mut_ptr()) })?;
+ Ok(unsafe { len.assume_init() } as usize)
+ }
+
+ pub fn configure(&self, cmd: &mut Command) {
+ match self {
+ // We `File::open`ed it when inheriting from environment,
+ // so no need to set cloexec for fifo.
+ Client::Fifo { .. } => return,
+ Client::Pipe { .. } => {}
+ };
+ // Here we basically just want to say that in the child process
+ // we'll configure the read/write file descriptors to *not* be
+ // cloexec, so they're inherited across the exec and specified as
+ // integers through `string_arg` above.
+ let read = self.read().as_raw_fd();
+ let write = self.write().as_raw_fd();
+ unsafe {
+ cmd.pre_exec(move || {
+ set_cloexec(read, false)?;
+ set_cloexec(write, false)?;
+ Ok(())
+ });
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Helper {
+ thread: JoinHandle<()>,
+ state: Arc<super::HelperState>,
+}
+
+pub(crate) fn spawn_helper(
+ client: crate::Client,
+ state: Arc<super::HelperState>,
+ mut f: Box<dyn FnMut(io::Result<crate::Acquired>) + Send>,
+) -> io::Result<Helper> {
+ static USR1_INIT: Once = Once::new();
+ let mut err = None;
+ USR1_INIT.call_once(|| unsafe {
+ let mut new: libc::sigaction = mem::zeroed();
+ #[cfg(target_os = "aix")]
+ {
+ new.sa_union.__su_sigaction = sigusr1_handler;
+ }
+ #[cfg(not(target_os = "aix"))]
+ {
+ new.sa_sigaction = sigusr1_handler as usize;
+ }
+ new.sa_flags = libc::SA_SIGINFO as _;
+ if libc::sigaction(libc::SIGUSR1, &new, ptr::null_mut()) != 0 {
+ err = Some(io::Error::last_os_error());
+ }
+ });
+
+ if let Some(e) = err.take() {
+ return Err(e);
+ }
+
+ let state2 = state.clone();
+ let thread = Builder::new().spawn(move || {
+ state2.for_each_request(|helper| loop {
+ match client.inner.acquire_allow_interrupts() {
+ Ok(Some(data)) => {
+ break f(Ok(crate::Acquired {
+ client: client.inner.clone(),
+ data,
+ disabled: false,
+ }));
+ }
+ Err(e) => break f(Err(e)),
+ Ok(None) if helper.producer_done() => break,
+ Ok(None) => {}
+ }
+ });
+ })?;
+
+ Ok(Helper { thread, state })
+}
+
+impl Helper {
+ pub fn join(self) {
+ let dur = Duration::from_millis(10);
+ let mut state = self.state.lock();
+ debug_assert!(state.producer_done);
+
+ // We need to join our helper thread, and it could be blocked in one
+ // of two locations. First is the wait for a request, but the
+ // initial drop of `HelperState` will take care of that. Otherwise
+ // it may be blocked in `client.acquire()`. We actually have no way
+ // of interrupting that, so resort to `pthread_kill` as a fallback.
+ // This signal should interrupt any blocking `read` call with
+ // `io::ErrorKind::Interrupt` and cause the thread to cleanly exit.
+ //
+ // Note that we don't do this forever though since there's a chance
+ // of bugs, so only do this opportunistically to make a best effort
+ // at clearing ourselves up.
+ for _ in 0..100 {
+ if state.consumer_done {
+ break;
+ }
+ unsafe {
+ // Ignore the return value here of `pthread_kill`,
+ // apparently on OSX if you kill a dead thread it will
+ // return an error, but on other platforms it may not. In
+ // that sense we don't actually know if this will succeed or
+ // not!
+ libc::pthread_kill(self.thread.as_pthread_t() as _, libc::SIGUSR1);
+ }
+ state = self
+ .state
+ .cvar
+ .wait_timeout(state, dur)
+ .unwrap_or_else(|e| e.into_inner())
+ .0;
+ thread::yield_now(); // we really want the other thread to run
+ }
+
+ // If we managed to actually see the consumer get done, then we can
+ // definitely wait for the thread. Otherwise it's... off in the ether
+ // I guess?
+ if state.consumer_done {
+ drop(self.thread.join());
+ }
+ }
+}
+
+unsafe fn fcntl_check(fd: c_int) -> Result<(), FromEnvErrorInner> {
+ match libc::fcntl(fd, libc::F_GETFD) {
+ -1 => Err(FromEnvErrorInner::CannotOpenFd(
+ fd,
+ io::Error::last_os_error(),
+ )),
+ _ => Ok(()),
+ }
+}
+
+unsafe fn fd_check(fd: c_int, check_pipe: bool) -> Result<(), FromEnvErrorInner> {
+ if check_pipe {
+ let mut stat = mem::zeroed();
+ if libc::fstat(fd, &mut stat) == -1 {
+ let last_os_error = io::Error::last_os_error();
+ fcntl_check(fd)?;
+ Err(FromEnvErrorInner::NotAPipe(fd, Some(last_os_error)))
+ } else {
+ // On android arm and i686 mode_t is u16 and st_mode is u32,
+ // this generates a type mismatch when S_IFIFO (declared as mode_t)
+ // is used in operations with st_mode, so we use this workaround
+ // to get the value of S_IFIFO with the same type of st_mode.
+ #[allow(unused_assignments)]
+ let mut s_ififo = stat.st_mode;
+ s_ififo = libc::S_IFIFO as _;
+ if stat.st_mode & s_ififo == s_ififo {
+ return Ok(());
+ }
+ Err(FromEnvErrorInner::NotAPipe(fd, None))
+ }
+ } else {
+ fcntl_check(fd)
+ }
+}
+
+fn set_cloexec(fd: c_int, set: bool) -> io::Result<()> {
+ unsafe {
+ let previous = cvt(libc::fcntl(fd, libc::F_GETFD))?;
+ let new = if set {
+ previous | libc::FD_CLOEXEC
+ } else {
+ previous & !libc::FD_CLOEXEC
+ };
+ if new != previous {
+ cvt(libc::fcntl(fd, libc::F_SETFD, new))?;
+ }
+ Ok(())
+ }
+}
+
+fn set_nonblocking(fd: c_int, set: bool) -> io::Result<()> {
+ let status_flag = if set { libc::O_NONBLOCK } else { 0 };
+
+ unsafe {
+ cvt(libc::fcntl(fd, libc::F_SETFL, status_flag))?;
+ }
+
+ Ok(())
+}
+
+fn cvt(t: c_int) -> io::Result<c_int> {
+ if t == -1 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(t)
+ }
+}
+
+extern "C" fn sigusr1_handler(
+ _signum: c_int,
+ _info: *mut libc::siginfo_t,
+ _ptr: *mut libc::c_void,
+) {
+ // nothing to do
+}
diff --git a/vendor/jobserver/src/wasm.rs b/vendor/jobserver/src/wasm.rs index 3793bd67c..3dda675fd 100644 --- a/vendor/jobserver/src/wasm.rs +++ b/vendor/jobserver/src/wasm.rs @@ -1,95 +1,96 @@ -use std::io; -use std::process::Command; -use std::sync::{Arc, Condvar, Mutex}; -use std::thread::{Builder, JoinHandle}; - -#[derive(Debug)] -pub struct Client { - inner: Arc<Inner>, -} - -#[derive(Debug)] -struct Inner { - count: Mutex<usize>, - cvar: Condvar, -} - -#[derive(Debug)] -pub struct Acquired(()); - -impl Client { - pub fn new(limit: usize) -> io::Result<Client> { - Ok(Client { - inner: Arc::new(Inner { - count: Mutex::new(limit), - cvar: Condvar::new(), - }), - }) - } - - pub unsafe fn open(_s: &str) -> Option<Client> { - None - } - - pub fn acquire(&self) -> io::Result<Acquired> { - let mut lock = self.inner.count.lock().unwrap_or_else(|e| e.into_inner()); - while *lock == 0 { - lock = self - .inner - .cvar - .wait(lock) - .unwrap_or_else(|e| e.into_inner()); - } - *lock -= 1; - Ok(Acquired(())) - } - - pub fn release(&self, _data: Option<&Acquired>) -> io::Result<()> { - let mut lock = self.inner.count.lock().unwrap_or_else(|e| e.into_inner()); - *lock += 1; - drop(lock); - self.inner.cvar.notify_one(); - Ok(()) - } - - pub fn string_arg(&self) -> String { - panic!( - "On this platform there is no cross process jobserver support, - so Client::configure is not supported." - ); - } - - pub fn available(&self) -> io::Result<usize> { - let lock = self.inner.count.lock().unwrap_or_else(|e| e.into_inner()); - Ok(*lock) - } - - pub fn configure(&self, _cmd: &mut Command) { - unreachable!(); - } -} - -#[derive(Debug)] -pub struct Helper { - thread: JoinHandle<()>, -} - -pub(crate) fn spawn_helper( - client: crate::Client, - state: Arc<super::HelperState>, - mut f: Box<dyn FnMut(io::Result<crate::Acquired>) + Send>, -) -> io::Result<Helper> { - let thread = Builder::new().spawn(move || { - state.for_each_request(|_| f(client.acquire())); - })?; - - Ok(Helper { thread: thread }) -} - -impl Helper { - pub fn join(self) { - // TODO: this is not correct if the thread is blocked in - // `client.acquire()`. - drop(self.thread.join()); - } -} +use crate::FromEnvErrorInner;
+use std::io;
+use std::process::Command;
+use std::sync::{Arc, Condvar, Mutex};
+use std::thread::{Builder, JoinHandle};
+
+#[derive(Debug)]
+pub struct Client {
+ inner: Arc<Inner>,
+}
+
+#[derive(Debug)]
+struct Inner {
+ count: Mutex<usize>,
+ cvar: Condvar,
+}
+
+#[derive(Debug)]
+pub struct Acquired(());
+
+impl Client {
+ pub fn new(limit: usize) -> io::Result<Client> {
+ Ok(Client {
+ inner: Arc::new(Inner {
+ count: Mutex::new(limit),
+ cvar: Condvar::new(),
+ }),
+ })
+ }
+
+ pub(crate) unsafe fn open(_s: &str, _check_pipe: bool) -> Result<Client, FromEnvErrorInner> {
+ Err(FromEnvErrorInner::Unsupported)
+ }
+
+ pub fn acquire(&self) -> io::Result<Acquired> {
+ let mut lock = self.inner.count.lock().unwrap_or_else(|e| e.into_inner());
+ while *lock == 0 {
+ lock = self
+ .inner
+ .cvar
+ .wait(lock)
+ .unwrap_or_else(|e| e.into_inner());
+ }
+ *lock -= 1;
+ Ok(Acquired(()))
+ }
+
+ pub fn release(&self, _data: Option<&Acquired>) -> io::Result<()> {
+ let mut lock = self.inner.count.lock().unwrap_or_else(|e| e.into_inner());
+ *lock += 1;
+ drop(lock);
+ self.inner.cvar.notify_one();
+ Ok(())
+ }
+
+ pub fn string_arg(&self) -> String {
+ panic!(
+ "On this platform there is no cross process jobserver support,
+ so Client::configure is not supported."
+ );
+ }
+
+ pub fn available(&self) -> io::Result<usize> {
+ let lock = self.inner.count.lock().unwrap_or_else(|e| e.into_inner());
+ Ok(*lock)
+ }
+
+ pub fn configure(&self, _cmd: &mut Command) {
+ unreachable!();
+ }
+}
+
+#[derive(Debug)]
+pub struct Helper {
+ thread: JoinHandle<()>,
+}
+
+pub(crate) fn spawn_helper(
+ client: crate::Client,
+ state: Arc<super::HelperState>,
+ mut f: Box<dyn FnMut(io::Result<crate::Acquired>) + Send>,
+) -> io::Result<Helper> {
+ let thread = Builder::new().spawn(move || {
+ state.for_each_request(|_| f(client.acquire()));
+ })?;
+
+ Ok(Helper { thread: thread })
+}
+
+impl Helper {
+ pub fn join(self) {
+ // TODO: this is not correct if the thread is blocked in
+ // `client.acquire()`.
+ drop(self.thread.join());
+ }
+}
diff --git a/vendor/jobserver/src/windows.rs b/vendor/jobserver/src/windows.rs index 6791efea4..bff89c1b0 100644 --- a/vendor/jobserver/src/windows.rs +++ b/vendor/jobserver/src/windows.rs @@ -1,266 +1,270 @@ -use std::ffi::CString; -use std::io; -use std::process::Command; -use std::ptr; -use std::sync::Arc; -use std::thread::{Builder, JoinHandle}; - -#[derive(Debug)] -pub struct Client { - sem: Handle, - name: String, -} - -#[derive(Debug)] -pub struct Acquired; - -type BOOL = i32; -type DWORD = u32; -type HANDLE = *mut u8; -type LONG = i32; - -const ERROR_ALREADY_EXISTS: DWORD = 183; -const FALSE: BOOL = 0; -const INFINITE: DWORD = 0xffffffff; -const SEMAPHORE_MODIFY_STATE: DWORD = 0x2; -const SYNCHRONIZE: DWORD = 0x00100000; -const TRUE: BOOL = 1; -const WAIT_OBJECT_0: DWORD = 0; - -extern "system" { - fn CloseHandle(handle: HANDLE) -> BOOL; - fn SetEvent(hEvent: HANDLE) -> BOOL; - fn WaitForMultipleObjects( - ncount: DWORD, - lpHandles: *const HANDLE, - bWaitAll: BOOL, - dwMilliseconds: DWORD, - ) -> DWORD; - fn CreateEventA( - lpEventAttributes: *mut u8, - bManualReset: BOOL, - bInitialState: BOOL, - lpName: *const i8, - ) -> HANDLE; - fn ReleaseSemaphore( - hSemaphore: HANDLE, - lReleaseCount: LONG, - lpPreviousCount: *mut LONG, - ) -> BOOL; - fn CreateSemaphoreA( - lpEventAttributes: *mut u8, - lInitialCount: LONG, - lMaximumCount: LONG, - lpName: *const i8, - ) -> HANDLE; - fn OpenSemaphoreA(dwDesiredAccess: DWORD, bInheritHandle: BOOL, lpName: *const i8) -> HANDLE; - fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) -> DWORD; - #[link_name = "SystemFunction036"] - fn RtlGenRandom(RandomBuffer: *mut u8, RandomBufferLength: u32) -> u8; -} - -// Note that we ideally would use the `getrandom` crate, but unfortunately -// that causes build issues when this crate is used in rust-lang/rust (see -// rust-lang/rust#65014 for more information). As a result we just inline -// the pretty simple Windows-specific implementation of generating -// randomness. -fn getrandom(dest: &mut [u8]) -> io::Result<()> { - // Prevent overflow of u32 - for chunk in dest.chunks_mut(u32::max_value() as usize) { - let ret = unsafe { RtlGenRandom(chunk.as_mut_ptr(), chunk.len() as u32) }; - if ret == 0 { - return Err(io::Error::new( - io::ErrorKind::Other, - "failed to generate random bytes", - )); - } - } - Ok(()) -} - -impl Client { - pub fn new(limit: usize) -> io::Result<Client> { - // Try a bunch of random semaphore names until we get a unique one, - // but don't try for too long. - // - // Note that `limit == 0` is a valid argument above but Windows - // won't let us create a semaphore with 0 slots available to it. Get - // `limit == 0` working by creating a semaphore instead with one - // slot and then immediately acquire it (without ever releaseing it - // back). - for _ in 0..100 { - let mut bytes = [0; 4]; - getrandom(&mut bytes)?; - let mut name = format!("__rust_jobserver_semaphore_{}\0", u32::from_ne_bytes(bytes)); - unsafe { - let create_limit = if limit == 0 { 1 } else { limit }; - let r = CreateSemaphoreA( - ptr::null_mut(), - create_limit as LONG, - create_limit as LONG, - name.as_ptr() as *const _, - ); - if r.is_null() { - return Err(io::Error::last_os_error()); - } - let handle = Handle(r); - - let err = io::Error::last_os_error(); - if err.raw_os_error() == Some(ERROR_ALREADY_EXISTS as i32) { - continue; - } - name.pop(); // chop off the trailing nul - let client = Client { - sem: handle, - name: name, - }; - if create_limit != limit { - client.acquire()?; - } - return Ok(client); - } - } - - Err(io::Error::new( - io::ErrorKind::Other, - "failed to find a unique name for a semaphore", - )) - } - - pub unsafe fn open(s: &str) -> Option<Client> { - let name = match CString::new(s) { - Ok(s) => s, - Err(_) => return None, - }; - - let sem = OpenSemaphoreA(SYNCHRONIZE | SEMAPHORE_MODIFY_STATE, FALSE, name.as_ptr()); - if sem.is_null() { - None - } else { - Some(Client { - sem: Handle(sem), - name: s.to_string(), - }) - } - } - - pub fn acquire(&self) -> io::Result<Acquired> { - unsafe { - let r = WaitForSingleObject(self.sem.0, INFINITE); - if r == WAIT_OBJECT_0 { - Ok(Acquired) - } else { - Err(io::Error::last_os_error()) - } - } - } - - pub fn release(&self, _data: Option<&Acquired>) -> io::Result<()> { - unsafe { - let r = ReleaseSemaphore(self.sem.0, 1, ptr::null_mut()); - if r != 0 { - Ok(()) - } else { - Err(io::Error::last_os_error()) - } - } - } - - pub fn string_arg(&self) -> String { - self.name.clone() - } - - pub fn available(&self) -> io::Result<usize> { - // Can't read value of a semaphore on Windows, so - // try to acquire without sleeping, since we can find out the - // old value on release. If acquisiton fails, then available is 0. - unsafe { - let r = WaitForSingleObject(self.sem.0, 0); - if r != WAIT_OBJECT_0 { - Ok(0) - } else { - let mut prev: LONG = 0; - let r = ReleaseSemaphore(self.sem.0, 1, &mut prev); - if r != 0 { - Ok(prev as usize + 1) - } else { - Err(io::Error::last_os_error()) - } - } - } - } - - pub fn configure(&self, _cmd: &mut Command) { - // nothing to do here, we gave the name of our semaphore to the - // child above - } -} - -#[derive(Debug)] -struct Handle(HANDLE); -// HANDLE is a raw ptr, but we're send/sync -unsafe impl Sync for Handle {} -unsafe impl Send for Handle {} - -impl Drop for Handle { - fn drop(&mut self) { - unsafe { - CloseHandle(self.0); - } - } -} - -#[derive(Debug)] -pub struct Helper { - event: Arc<Handle>, - thread: JoinHandle<()>, -} - -pub(crate) fn spawn_helper( - client: crate::Client, - state: Arc<super::HelperState>, - mut f: Box<dyn FnMut(io::Result<crate::Acquired>) + Send>, -) -> io::Result<Helper> { - let event = unsafe { - let r = CreateEventA(ptr::null_mut(), TRUE, FALSE, ptr::null()); - if r.is_null() { - return Err(io::Error::last_os_error()); - } else { - Handle(r) - } - }; - let event = Arc::new(event); - let event2 = event.clone(); - let thread = Builder::new().spawn(move || { - let objects = [event2.0, client.inner.sem.0]; - state.for_each_request(|_| { - const WAIT_OBJECT_1: u32 = WAIT_OBJECT_0 + 1; - match unsafe { WaitForMultipleObjects(2, objects.as_ptr(), FALSE, INFINITE) } { - WAIT_OBJECT_0 => return, - WAIT_OBJECT_1 => f(Ok(crate::Acquired { - client: client.inner.clone(), - data: Acquired, - disabled: false, - })), - _ => f(Err(io::Error::last_os_error())), - } - }); - })?; - Ok(Helper { thread, event }) -} - -impl Helper { - pub fn join(self) { - // Unlike unix this logic is much easier. If our thread was blocked - // in waiting for requests it should already be woken up and - // exiting. Otherwise it's waiting for a token, so we wake it up - // with a different event that it's also waiting on here. After - // these two we should be guaranteed the thread is on its way out, - // so we can safely `join`. - let r = unsafe { SetEvent(self.event.0) }; - if r == 0 { - panic!("failed to set event: {}", io::Error::last_os_error()); - } - drop(self.thread.join()); - } -} +use crate::FromEnvErrorInner;
+use std::ffi::CString;
+use std::io;
+use std::process::Command;
+use std::ptr;
+use std::sync::Arc;
+use std::thread::{Builder, JoinHandle};
+
+#[derive(Debug)]
+pub struct Client {
+ sem: Handle,
+ name: String,
+}
+
+#[derive(Debug)]
+pub struct Acquired;
+
+type BOOL = i32;
+type DWORD = u32;
+type HANDLE = *mut u8;
+type LONG = i32;
+
+const ERROR_ALREADY_EXISTS: DWORD = 183;
+const FALSE: BOOL = 0;
+const INFINITE: DWORD = 0xffffffff;
+const SEMAPHORE_MODIFY_STATE: DWORD = 0x2;
+const SYNCHRONIZE: DWORD = 0x00100000;
+const TRUE: BOOL = 1;
+const WAIT_OBJECT_0: DWORD = 0;
+
+extern "system" {
+ fn CloseHandle(handle: HANDLE) -> BOOL;
+ fn SetEvent(hEvent: HANDLE) -> BOOL;
+ fn WaitForMultipleObjects(
+ ncount: DWORD,
+ lpHandles: *const HANDLE,
+ bWaitAll: BOOL,
+ dwMilliseconds: DWORD,
+ ) -> DWORD;
+ fn CreateEventA(
+ lpEventAttributes: *mut u8,
+ bManualReset: BOOL,
+ bInitialState: BOOL,
+ lpName: *const i8,
+ ) -> HANDLE;
+ fn ReleaseSemaphore(
+ hSemaphore: HANDLE,
+ lReleaseCount: LONG,
+ lpPreviousCount: *mut LONG,
+ ) -> BOOL;
+ fn CreateSemaphoreA(
+ lpEventAttributes: *mut u8,
+ lInitialCount: LONG,
+ lMaximumCount: LONG,
+ lpName: *const i8,
+ ) -> HANDLE;
+ fn OpenSemaphoreA(dwDesiredAccess: DWORD, bInheritHandle: BOOL, lpName: *const i8) -> HANDLE;
+ fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) -> DWORD;
+ #[link_name = "SystemFunction036"]
+ fn RtlGenRandom(RandomBuffer: *mut u8, RandomBufferLength: u32) -> u8;
+}
+
+// Note that we ideally would use the `getrandom` crate, but unfortunately
+// that causes build issues when this crate is used in rust-lang/rust (see
+// rust-lang/rust#65014 for more information). As a result we just inline
+// the pretty simple Windows-specific implementation of generating
+// randomness.
+fn getrandom(dest: &mut [u8]) -> io::Result<()> {
+ // Prevent overflow of u32
+ for chunk in dest.chunks_mut(u32::max_value() as usize) {
+ let ret = unsafe { RtlGenRandom(chunk.as_mut_ptr(), chunk.len() as u32) };
+ if ret == 0 {
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ "failed to generate random bytes",
+ ));
+ }
+ }
+ Ok(())
+}
+
+impl Client {
+ pub fn new(limit: usize) -> io::Result<Client> {
+ // Try a bunch of random semaphore names until we get a unique one,
+ // but don't try for too long.
+ //
+ // Note that `limit == 0` is a valid argument above but Windows
+ // won't let us create a semaphore with 0 slots available to it. Get
+ // `limit == 0` working by creating a semaphore instead with one
+ // slot and then immediately acquire it (without ever releaseing it
+ // back).
+ for _ in 0..100 {
+ let mut bytes = [0; 4];
+ getrandom(&mut bytes)?;
+ let mut name = format!("__rust_jobserver_semaphore_{}\0", u32::from_ne_bytes(bytes));
+ unsafe {
+ let create_limit = if limit == 0 { 1 } else { limit };
+ let r = CreateSemaphoreA(
+ ptr::null_mut(),
+ create_limit as LONG,
+ create_limit as LONG,
+ name.as_ptr() as *const _,
+ );
+ if r.is_null() {
+ return Err(io::Error::last_os_error());
+ }
+ let handle = Handle(r);
+
+ let err = io::Error::last_os_error();
+ if err.raw_os_error() == Some(ERROR_ALREADY_EXISTS as i32) {
+ continue;
+ }
+ name.pop(); // chop off the trailing nul
+ let client = Client {
+ sem: handle,
+ name: name,
+ };
+ if create_limit != limit {
+ client.acquire()?;
+ }
+ return Ok(client);
+ }
+ }
+
+ Err(io::Error::new(
+ io::ErrorKind::Other,
+ "failed to find a unique name for a semaphore",
+ ))
+ }
+
+ pub(crate) unsafe fn open(s: &str, _check_pipe: bool) -> Result<Client, FromEnvErrorInner> {
+ let name = match CString::new(s) {
+ Ok(s) => s,
+ Err(e) => return Err(FromEnvErrorInner::CannotParse(e.to_string())),
+ };
+
+ let sem = OpenSemaphoreA(SYNCHRONIZE | SEMAPHORE_MODIFY_STATE, FALSE, name.as_ptr());
+ if sem.is_null() {
+ Err(FromEnvErrorInner::CannotOpenPath(
+ s.to_string(),
+ io::Error::last_os_error(),
+ ))
+ } else {
+ Ok(Client {
+ sem: Handle(sem),
+ name: s.to_string(),
+ })
+ }
+ }
+
+ pub fn acquire(&self) -> io::Result<Acquired> {
+ unsafe {
+ let r = WaitForSingleObject(self.sem.0, INFINITE);
+ if r == WAIT_OBJECT_0 {
+ Ok(Acquired)
+ } else {
+ Err(io::Error::last_os_error())
+ }
+ }
+ }
+
+ pub fn release(&self, _data: Option<&Acquired>) -> io::Result<()> {
+ unsafe {
+ let r = ReleaseSemaphore(self.sem.0, 1, ptr::null_mut());
+ if r != 0 {
+ Ok(())
+ } else {
+ Err(io::Error::last_os_error())
+ }
+ }
+ }
+
+ pub fn string_arg(&self) -> String {
+ self.name.clone()
+ }
+
+ pub fn available(&self) -> io::Result<usize> {
+ // Can't read value of a semaphore on Windows, so
+ // try to acquire without sleeping, since we can find out the
+ // old value on release. If acquisiton fails, then available is 0.
+ unsafe {
+ let r = WaitForSingleObject(self.sem.0, 0);
+ if r != WAIT_OBJECT_0 {
+ Ok(0)
+ } else {
+ let mut prev: LONG = 0;
+ let r = ReleaseSemaphore(self.sem.0, 1, &mut prev);
+ if r != 0 {
+ Ok(prev as usize + 1)
+ } else {
+ Err(io::Error::last_os_error())
+ }
+ }
+ }
+ }
+
+ pub fn configure(&self, _cmd: &mut Command) {
+ // nothing to do here, we gave the name of our semaphore to the
+ // child above
+ }
+}
+
+#[derive(Debug)]
+struct Handle(HANDLE);
+// HANDLE is a raw ptr, but we're send/sync
+unsafe impl Sync for Handle {}
+unsafe impl Send for Handle {}
+
+impl Drop for Handle {
+ fn drop(&mut self) {
+ unsafe {
+ CloseHandle(self.0);
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Helper {
+ event: Arc<Handle>,
+ thread: JoinHandle<()>,
+}
+
+pub(crate) fn spawn_helper(
+ client: crate::Client,
+ state: Arc<super::HelperState>,
+ mut f: Box<dyn FnMut(io::Result<crate::Acquired>) + Send>,
+) -> io::Result<Helper> {
+ let event = unsafe {
+ let r = CreateEventA(ptr::null_mut(), TRUE, FALSE, ptr::null());
+ if r.is_null() {
+ return Err(io::Error::last_os_error());
+ } else {
+ Handle(r)
+ }
+ };
+ let event = Arc::new(event);
+ let event2 = event.clone();
+ let thread = Builder::new().spawn(move || {
+ let objects = [event2.0, client.inner.sem.0];
+ state.for_each_request(|_| {
+ const WAIT_OBJECT_1: u32 = WAIT_OBJECT_0 + 1;
+ match unsafe { WaitForMultipleObjects(2, objects.as_ptr(), FALSE, INFINITE) } {
+ WAIT_OBJECT_0 => return,
+ WAIT_OBJECT_1 => f(Ok(crate::Acquired {
+ client: client.inner.clone(),
+ data: Acquired,
+ disabled: false,
+ })),
+ _ => f(Err(io::Error::last_os_error())),
+ }
+ });
+ })?;
+ Ok(Helper { thread, event })
+}
+
+impl Helper {
+ pub fn join(self) {
+ // Unlike unix this logic is much easier. If our thread was blocked
+ // in waiting for requests it should already be woken up and
+ // exiting. Otherwise it's waiting for a token, so we wake it up
+ // with a different event that it's also waiting on here. After
+ // these two we should be guaranteed the thread is on its way out,
+ // so we can safely `join`.
+ let r = unsafe { SetEvent(self.event.0) };
+ if r == 0 {
+ panic!("failed to set event: {}", io::Error::last_os_error());
+ }
+ drop(self.thread.join());
+ }
+}
diff --git a/vendor/jobserver/tests/client-of-myself.rs b/vendor/jobserver/tests/client-of-myself.rs index 45d57b0b5..bb690b5aa 100644 --- a/vendor/jobserver/tests/client-of-myself.rs +++ b/vendor/jobserver/tests/client-of-myself.rs @@ -1,59 +1,59 @@ -use std::env; -use std::io::prelude::*; -use std::io::BufReader; -use std::process::{Command, Stdio}; -use std::sync::mpsc; -use std::thread; - -use jobserver::Client; - -macro_rules! t { - ($e:expr) => { - match $e { - Ok(e) => e, - Err(e) => panic!("{} failed with {}", stringify!($e), e), - } - }; -} - -fn main() { - if env::var("I_AM_THE_CLIENT").is_ok() { - client(); - } else { - server(); - } -} - -fn server() { - let me = t!(env::current_exe()); - let client = t!(Client::new(1)); - let mut cmd = Command::new(me); - cmd.env("I_AM_THE_CLIENT", "1").stdout(Stdio::piped()); - client.configure(&mut cmd); - let acq = client.acquire().unwrap(); - let mut child = t!(cmd.spawn()); - let stdout = child.stdout.take().unwrap(); - let (tx, rx) = mpsc::channel(); - let t = thread::spawn(move || { - for line in BufReader::new(stdout).lines() { - tx.send(t!(line)).unwrap(); - } - }); - - for _ in 0..100 { - assert!(rx.try_recv().is_err()); - } - - drop(acq); - assert_eq!(rx.recv().unwrap(), "hello!"); - t.join().unwrap(); - assert!(rx.recv().is_err()); - client.acquire().unwrap(); -} - -fn client() { - let client = unsafe { Client::from_env().unwrap() }; - let acq = client.acquire().unwrap(); - println!("hello!"); - drop(acq); -} +use std::env;
+use std::io::prelude::*;
+use std::io::BufReader;
+use std::process::{Command, Stdio};
+use std::sync::mpsc;
+use std::thread;
+
+use jobserver::Client;
+
+macro_rules! t {
+ ($e:expr) => {
+ match $e {
+ Ok(e) => e,
+ Err(e) => panic!("{} failed with {}", stringify!($e), e),
+ }
+ };
+}
+
+fn main() {
+ if env::var("I_AM_THE_CLIENT").is_ok() {
+ client();
+ } else {
+ server();
+ }
+}
+
+fn server() {
+ let me = t!(env::current_exe());
+ let client = t!(Client::new(1));
+ let mut cmd = Command::new(me);
+ cmd.env("I_AM_THE_CLIENT", "1").stdout(Stdio::piped());
+ client.configure(&mut cmd);
+ let acq = client.acquire().unwrap();
+ let mut child = t!(cmd.spawn());
+ let stdout = child.stdout.take().unwrap();
+ let (tx, rx) = mpsc::channel();
+ let t = thread::spawn(move || {
+ for line in BufReader::new(stdout).lines() {
+ tx.send(t!(line)).unwrap();
+ }
+ });
+
+ for _ in 0..100 {
+ assert!(rx.try_recv().is_err());
+ }
+
+ drop(acq);
+ assert_eq!(rx.recv().unwrap(), "hello!");
+ t.join().unwrap();
+ assert!(rx.recv().is_err());
+ client.acquire().unwrap();
+}
+
+fn client() {
+ let client = unsafe { Client::from_env().unwrap() };
+ let acq = client.acquire().unwrap();
+ println!("hello!");
+ drop(acq);
+}
diff --git a/vendor/jobserver/tests/client.rs b/vendor/jobserver/tests/client.rs index 2516b8ccf..6c0420a6d 100644 --- a/vendor/jobserver/tests/client.rs +++ b/vendor/jobserver/tests/client.rs @@ -1,198 +1,198 @@ -use std::env; -use std::fs::File; -use std::io::Write; -use std::process::Command; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc; -use std::sync::Arc; -use std::thread; - -use futures::future::{self, Future}; -use futures::stream::{self, Stream}; -use jobserver::Client; -use tokio_core::reactor::Core; -use tokio_process::CommandExt; - -macro_rules! t { - ($e:expr) => { - match $e { - Ok(e) => e, - Err(e) => panic!("{} failed with {}", stringify!($e), e), - } - }; -} - -struct Test { - name: &'static str, - f: &'static dyn Fn(), - make_args: &'static [&'static str], - rule: &'static dyn Fn(&str) -> String, -} - -const TESTS: &[Test] = &[ - Test { - name: "no j args", - make_args: &[], - rule: &|me| me.to_string(), - f: &|| { - assert!(unsafe { Client::from_env().is_none() }); - }, - }, - Test { - name: "no j args with plus", - make_args: &[], - rule: &|me| format!("+{}", me), - f: &|| { - assert!(unsafe { Client::from_env().is_none() }); - }, - }, - Test { - name: "j args with plus", - make_args: &["-j2"], - rule: &|me| format!("+{}", me), - f: &|| { - assert!(unsafe { Client::from_env().is_some() }); - }, - }, - Test { - name: "acquire", - make_args: &["-j2"], - rule: &|me| format!("+{}", me), - f: &|| { - let c = unsafe { Client::from_env().unwrap() }; - drop(c.acquire().unwrap()); - drop(c.acquire().unwrap()); - }, - }, - Test { - name: "acquire3", - make_args: &["-j3"], - rule: &|me| format!("+{}", me), - f: &|| { - let c = unsafe { Client::from_env().unwrap() }; - let a = c.acquire().unwrap(); - let b = c.acquire().unwrap(); - drop((a, b)); - }, - }, - Test { - name: "acquire blocks", - make_args: &["-j2"], - rule: &|me| format!("+{}", me), - f: &|| { - let c = unsafe { Client::from_env().unwrap() }; - let a = c.acquire().unwrap(); - let hit = Arc::new(AtomicBool::new(false)); - let hit2 = hit.clone(); - let (tx, rx) = mpsc::channel(); - let t = thread::spawn(move || { - tx.send(()).unwrap(); - let _b = c.acquire().unwrap(); - hit2.store(true, Ordering::SeqCst); - }); - rx.recv().unwrap(); - assert!(!hit.load(Ordering::SeqCst)); - drop(a); - t.join().unwrap(); - assert!(hit.load(Ordering::SeqCst)); - }, - }, - Test { - name: "acquire_raw", - make_args: &["-j2"], - rule: &|me| format!("+{}", me), - f: &|| { - let c = unsafe { Client::from_env().unwrap() }; - c.acquire_raw().unwrap(); - c.release_raw().unwrap(); - }, - }, -]; - -fn main() { - if let Ok(test) = env::var("TEST_TO_RUN") { - return (TESTS.iter().find(|t| t.name == test).unwrap().f)(); - } - - let me = t!(env::current_exe()); - let me = me.to_str().unwrap(); - let filter = env::args().nth(1); - - let mut core = t!(Core::new()); - - let futures = TESTS - .iter() - .filter(|test| match filter { - Some(ref s) => test.name.contains(s), - None => true, - }) - .map(|test| { - let td = t!(tempfile::tempdir()); - let makefile = format!( - "\ -all: export TEST_TO_RUN={} -all: -\t{} -", - test.name, - (test.rule)(me) - ); - t!(t!(File::create(td.path().join("Makefile"))).write_all(makefile.as_bytes())); - let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string()); - let mut cmd = Command::new(prog); - cmd.args(test.make_args); - cmd.current_dir(td.path()); - future::lazy(move || { - cmd.output_async().map(move |e| { - drop(td); - (test, e) - }) - }) - }) - .collect::<Vec<_>>(); - - println!("\nrunning {} tests\n", futures.len()); - - let stream = stream::iter(futures.into_iter().map(Ok)).buffer_unordered(num_cpus::get()); - - let mut failures = Vec::new(); - t!(core.run(stream.for_each(|(test, output)| { - if output.status.success() { - println!("test {} ... ok", test.name); - } else { - println!("test {} ... FAIL", test.name); - failures.push((test, output)); - } - Ok(()) - }))); - - if failures.is_empty() { - println!("\ntest result: ok\n"); - return; - } - - println!("\n----------- failures"); - - for (test, output) in failures { - println!("test {}", test.name); - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - - println!("\texit status: {}", output.status); - if !stdout.is_empty() { - println!("\tstdout ==="); - for line in stdout.lines() { - println!("\t\t{}", line); - } - } - - if !stderr.is_empty() { - println!("\tstderr ==="); - for line in stderr.lines() { - println!("\t\t{}", line); - } - } - } - - std::process::exit(4); -} +use std::env;
+use std::fs::File;
+use std::io::Write;
+use std::process::Command;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::mpsc;
+use std::sync::Arc;
+use std::thread;
+
+use futures::future::{self, Future};
+use futures::stream::{self, Stream};
+use jobserver::Client;
+use tokio_core::reactor::Core;
+use tokio_process::CommandExt;
+
+macro_rules! t {
+ ($e:expr) => {
+ match $e {
+ Ok(e) => e,
+ Err(e) => panic!("{} failed with {}", stringify!($e), e),
+ }
+ };
+}
+
+struct Test {
+ name: &'static str,
+ f: &'static dyn Fn(),
+ make_args: &'static [&'static str],
+ rule: &'static dyn Fn(&str) -> String,
+}
+
+const TESTS: &[Test] = &[
+ Test {
+ name: "no j args",
+ make_args: &[],
+ rule: &|me| me.to_string(),
+ f: &|| {
+ assert!(unsafe { Client::from_env().is_none() });
+ },
+ },
+ Test {
+ name: "no j args with plus",
+ make_args: &[],
+ rule: &|me| format!("+{}", me),
+ f: &|| {
+ assert!(unsafe { Client::from_env().is_none() });
+ },
+ },
+ Test {
+ name: "j args with plus",
+ make_args: &["-j2"],
+ rule: &|me| format!("+{}", me),
+ f: &|| {
+ assert!(unsafe { Client::from_env().is_some() });
+ },
+ },
+ Test {
+ name: "acquire",
+ make_args: &["-j2"],
+ rule: &|me| format!("+{}", me),
+ f: &|| {
+ let c = unsafe { Client::from_env().unwrap() };
+ drop(c.acquire().unwrap());
+ drop(c.acquire().unwrap());
+ },
+ },
+ Test {
+ name: "acquire3",
+ make_args: &["-j3"],
+ rule: &|me| format!("+{}", me),
+ f: &|| {
+ let c = unsafe { Client::from_env().unwrap() };
+ let a = c.acquire().unwrap();
+ let b = c.acquire().unwrap();
+ drop((a, b));
+ },
+ },
+ Test {
+ name: "acquire blocks",
+ make_args: &["-j2"],
+ rule: &|me| format!("+{}", me),
+ f: &|| {
+ let c = unsafe { Client::from_env().unwrap() };
+ let a = c.acquire().unwrap();
+ let hit = Arc::new(AtomicBool::new(false));
+ let hit2 = hit.clone();
+ let (tx, rx) = mpsc::channel();
+ let t = thread::spawn(move || {
+ tx.send(()).unwrap();
+ let _b = c.acquire().unwrap();
+ hit2.store(true, Ordering::SeqCst);
+ });
+ rx.recv().unwrap();
+ assert!(!hit.load(Ordering::SeqCst));
+ drop(a);
+ t.join().unwrap();
+ assert!(hit.load(Ordering::SeqCst));
+ },
+ },
+ Test {
+ name: "acquire_raw",
+ make_args: &["-j2"],
+ rule: &|me| format!("+{}", me),
+ f: &|| {
+ let c = unsafe { Client::from_env().unwrap() };
+ c.acquire_raw().unwrap();
+ c.release_raw().unwrap();
+ },
+ },
+];
+
+fn main() {
+ if let Ok(test) = env::var("TEST_TO_RUN") {
+ return (TESTS.iter().find(|t| t.name == test).unwrap().f)();
+ }
+
+ let me = t!(env::current_exe());
+ let me = me.to_str().unwrap();
+ let filter = env::args().nth(1);
+
+ let mut core = t!(Core::new());
+
+ let futures = TESTS
+ .iter()
+ .filter(|test| match filter {
+ Some(ref s) => test.name.contains(s),
+ None => true,
+ })
+ .map(|test| {
+ let td = t!(tempfile::tempdir());
+ let makefile = format!(
+ "\
+all: export TEST_TO_RUN={}
+all:
+\t{}
+",
+ test.name,
+ (test.rule)(me)
+ );
+ t!(t!(File::create(td.path().join("Makefile"))).write_all(makefile.as_bytes()));
+ let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string());
+ let mut cmd = Command::new(prog);
+ cmd.args(test.make_args);
+ cmd.current_dir(td.path());
+ future::lazy(move || {
+ cmd.output_async().map(move |e| {
+ drop(td);
+ (test, e)
+ })
+ })
+ })
+ .collect::<Vec<_>>();
+
+ println!("\nrunning {} tests\n", futures.len());
+
+ let stream = stream::iter(futures.into_iter().map(Ok)).buffer_unordered(num_cpus::get());
+
+ let mut failures = Vec::new();
+ t!(core.run(stream.for_each(|(test, output)| {
+ if output.status.success() {
+ println!("test {} ... ok", test.name);
+ } else {
+ println!("test {} ... FAIL", test.name);
+ failures.push((test, output));
+ }
+ Ok(())
+ })));
+
+ if failures.is_empty() {
+ println!("\ntest result: ok\n");
+ return;
+ }
+
+ println!("\n----------- failures");
+
+ for (test, output) in failures {
+ println!("test {}", test.name);
+ let stdout = String::from_utf8_lossy(&output.stdout);
+ let stderr = String::from_utf8_lossy(&output.stderr);
+
+ println!("\texit status: {}", output.status);
+ if !stdout.is_empty() {
+ println!("\tstdout ===");
+ for line in stdout.lines() {
+ println!("\t\t{}", line);
+ }
+ }
+
+ if !stderr.is_empty() {
+ println!("\tstderr ===");
+ for line in stderr.lines() {
+ println!("\t\t{}", line);
+ }
+ }
+ }
+
+ std::process::exit(4);
+}
diff --git a/vendor/jobserver/tests/helper.rs b/vendor/jobserver/tests/helper.rs index 0b3ba88a7..ef4b62da5 100644 --- a/vendor/jobserver/tests/helper.rs +++ b/vendor/jobserver/tests/helper.rs @@ -1,77 +1,77 @@ -use jobserver::Client; -use std::sync::atomic::*; -use std::sync::mpsc; -use std::sync::*; - -macro_rules! t { - ($e:expr) => { - match $e { - Ok(e) => e, - Err(e) => panic!("{} failed with {}", stringify!($e), e), - } - }; -} - -#[test] -fn helper_smoke() { - let client = t!(Client::new(1)); - drop(client.clone().into_helper_thread(|_| ()).unwrap()); - drop(client.clone().into_helper_thread(|_| ()).unwrap()); - drop(client.clone().into_helper_thread(|_| ()).unwrap()); - drop(client.clone().into_helper_thread(|_| ()).unwrap()); - drop(client.clone().into_helper_thread(|_| ()).unwrap()); - drop(client.into_helper_thread(|_| ()).unwrap()); -} - -#[test] -fn acquire() { - let (tx, rx) = mpsc::channel(); - let client = t!(Client::new(1)); - let helper = client - .into_helper_thread(move |a| drop(tx.send(a))) - .unwrap(); - assert!(rx.try_recv().is_err()); - helper.request_token(); - rx.recv().unwrap().unwrap(); - helper.request_token(); - rx.recv().unwrap().unwrap(); - - helper.request_token(); - helper.request_token(); - rx.recv().unwrap().unwrap(); - rx.recv().unwrap().unwrap(); - - helper.request_token(); - helper.request_token(); - drop(helper); -} - -#[test] -fn prompt_shutdown() { - for _ in 0..100 { - let client = jobserver::Client::new(4).unwrap(); - let count = Arc::new(AtomicU32::new(0)); - let count2 = count.clone(); - let tokens = Arc::new(Mutex::new(Vec::new())); - let helper = client - .into_helper_thread(move |token| { - tokens.lock().unwrap().push(token); - count2.fetch_add(1, Ordering::SeqCst); - }) - .unwrap(); - - // Request more tokens than what are available. - for _ in 0..5 { - helper.request_token(); - } - // Wait for at least some of the requests to finish. - while count.load(Ordering::SeqCst) < 3 { - std::thread::yield_now(); - } - // Drop helper - let t = std::time::Instant::now(); - drop(helper); - let d = t.elapsed(); - assert!(d.as_secs_f64() < 0.5); - } -} +use jobserver::Client;
+use std::sync::atomic::*;
+use std::sync::mpsc;
+use std::sync::*;
+
+macro_rules! t {
+ ($e:expr) => {
+ match $e {
+ Ok(e) => e,
+ Err(e) => panic!("{} failed with {}", stringify!($e), e),
+ }
+ };
+}
+
+#[test]
+fn helper_smoke() {
+ let client = t!(Client::new(1));
+ drop(client.clone().into_helper_thread(|_| ()).unwrap());
+ drop(client.clone().into_helper_thread(|_| ()).unwrap());
+ drop(client.clone().into_helper_thread(|_| ()).unwrap());
+ drop(client.clone().into_helper_thread(|_| ()).unwrap());
+ drop(client.clone().into_helper_thread(|_| ()).unwrap());
+ drop(client.into_helper_thread(|_| ()).unwrap());
+}
+
+#[test]
+fn acquire() {
+ let (tx, rx) = mpsc::channel();
+ let client = t!(Client::new(1));
+ let helper = client
+ .into_helper_thread(move |a| drop(tx.send(a)))
+ .unwrap();
+ assert!(rx.try_recv().is_err());
+ helper.request_token();
+ rx.recv().unwrap().unwrap();
+ helper.request_token();
+ rx.recv().unwrap().unwrap();
+
+ helper.request_token();
+ helper.request_token();
+ rx.recv().unwrap().unwrap();
+ rx.recv().unwrap().unwrap();
+
+ helper.request_token();
+ helper.request_token();
+ drop(helper);
+}
+
+#[test]
+fn prompt_shutdown() {
+ for _ in 0..100 {
+ let client = jobserver::Client::new(4).unwrap();
+ let count = Arc::new(AtomicU32::new(0));
+ let count2 = count.clone();
+ let tokens = Arc::new(Mutex::new(Vec::new()));
+ let helper = client
+ .into_helper_thread(move |token| {
+ tokens.lock().unwrap().push(token);
+ count2.fetch_add(1, Ordering::SeqCst);
+ })
+ .unwrap();
+
+ // Request more tokens than what are available.
+ for _ in 0..5 {
+ helper.request_token();
+ }
+ // Wait for at least some of the requests to finish.
+ while count.load(Ordering::SeqCst) < 3 {
+ std::thread::yield_now();
+ }
+ // Drop helper
+ let t = std::time::Instant::now();
+ drop(helper);
+ let d = t.elapsed();
+ assert!(d.as_secs_f64() < 0.5);
+ }
+}
diff --git a/vendor/jobserver/tests/make-as-a-client.rs b/vendor/jobserver/tests/make-as-a-client.rs index 4faac5b88..621754dec 100644 --- a/vendor/jobserver/tests/make-as-a-client.rs +++ b/vendor/jobserver/tests/make-as-a-client.rs @@ -1,81 +1,81 @@ -use std::env; -use std::fs::File; -use std::io::prelude::*; -use std::net::{TcpListener, TcpStream}; -use std::process::Command; - -use jobserver::Client; - -macro_rules! t { - ($e:expr) => { - match $e { - Ok(e) => e, - Err(e) => panic!("{} failed with {}", stringify!($e), e), - } - }; -} - -fn main() { - if env::var("_DO_THE_TEST").is_ok() { - std::process::exit( - Command::new(env::var_os("MAKE").unwrap()) - .env("MAKEFLAGS", env::var_os("CARGO_MAKEFLAGS").unwrap()) - .env_remove("_DO_THE_TEST") - .args(&env::args_os().skip(1).collect::<Vec<_>>()) - .status() - .unwrap() - .code() - .unwrap_or(1), - ); - } - - if let Ok(s) = env::var("TEST_ADDR") { - let mut contents = Vec::new(); - t!(t!(TcpStream::connect(&s)).read_to_end(&mut contents)); - return; - } - - let c = t!(Client::new(1)); - let td = tempfile::tempdir().unwrap(); - - let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string()); - - let me = t!(env::current_exe()); - let me = me.to_str().unwrap(); - - let mut cmd = Command::new(&me); - cmd.current_dir(td.path()); - cmd.env("MAKE", prog); - cmd.env("_DO_THE_TEST", "1"); - - t!(t!(File::create(td.path().join("Makefile"))).write_all( - format!( - "\ -all: foo bar -foo: -\t{0} -bar: -\t{0} -", - me - ) - .as_bytes() - )); - - // We're leaking one extra token to `make` sort of violating the makefile - // jobserver protocol. It has the desired effect though. - c.configure(&mut cmd); - - let listener = t!(TcpListener::bind("127.0.0.1:0")); - let addr = t!(listener.local_addr()); - cmd.env("TEST_ADDR", addr.to_string()); - let mut child = t!(cmd.spawn()); - - // We should get both connections as the two programs should be run - // concurrently. - let a = t!(listener.accept()); - let b = t!(listener.accept()); - drop((a, b)); - - assert!(t!(child.wait()).success()); -} +use std::env;
+use std::fs::File;
+use std::io::prelude::*;
+use std::net::{TcpListener, TcpStream};
+use std::process::Command;
+
+use jobserver::Client;
+
+macro_rules! t {
+ ($e:expr) => {
+ match $e {
+ Ok(e) => e,
+ Err(e) => panic!("{} failed with {}", stringify!($e), e),
+ }
+ };
+}
+
+fn main() {
+ if env::var("_DO_THE_TEST").is_ok() {
+ std::process::exit(
+ Command::new(env::var_os("MAKE").unwrap())
+ .env("MAKEFLAGS", env::var_os("CARGO_MAKEFLAGS").unwrap())
+ .env_remove("_DO_THE_TEST")
+ .args(&env::args_os().skip(1).collect::<Vec<_>>())
+ .status()
+ .unwrap()
+ .code()
+ .unwrap_or(1),
+ );
+ }
+
+ if let Ok(s) = env::var("TEST_ADDR") {
+ let mut contents = Vec::new();
+ t!(t!(TcpStream::connect(&s)).read_to_end(&mut contents));
+ return;
+ }
+
+ let c = t!(Client::new(1));
+ let td = tempfile::tempdir().unwrap();
+
+ let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string());
+
+ let me = t!(env::current_exe());
+ let me = me.to_str().unwrap();
+
+ let mut cmd = Command::new(&me);
+ cmd.current_dir(td.path());
+ cmd.env("MAKE", prog);
+ cmd.env("_DO_THE_TEST", "1");
+
+ t!(t!(File::create(td.path().join("Makefile"))).write_all(
+ format!(
+ "\
+all: foo bar
+foo:
+\t{0}
+bar:
+\t{0}
+",
+ me
+ )
+ .as_bytes()
+ ));
+
+ // We're leaking one extra token to `make` sort of violating the makefile
+ // jobserver protocol. It has the desired effect though.
+ c.configure(&mut cmd);
+
+ let listener = t!(TcpListener::bind("127.0.0.1:0"));
+ let addr = t!(listener.local_addr());
+ cmd.env("TEST_ADDR", addr.to_string());
+ let mut child = t!(cmd.spawn());
+
+ // We should get both connections as the two programs should be run
+ // concurrently.
+ let a = t!(listener.accept());
+ let b = t!(listener.accept());
+ drop((a, b));
+
+ assert!(t!(child.wait()).success());
+}
diff --git a/vendor/jobserver/tests/server.rs b/vendor/jobserver/tests/server.rs index 70ea218fc..862f66cad 100644 --- a/vendor/jobserver/tests/server.rs +++ b/vendor/jobserver/tests/server.rs @@ -1,181 +1,181 @@ -use std::env; -use std::fs::File; -use std::io::prelude::*; -use std::process::Command; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc; -use std::sync::Arc; -use std::thread; - -use jobserver::Client; - -macro_rules! t { - ($e:expr) => { - match $e { - Ok(e) => e, - Err(e) => panic!("{} failed with {}", stringify!($e), e), - } - }; -} - -#[test] -fn server_smoke() { - let c = t!(Client::new(1)); - drop(c.acquire().unwrap()); - drop(c.acquire().unwrap()); -} - -#[test] -fn server_multiple() { - let c = t!(Client::new(2)); - let a = c.acquire().unwrap(); - let b = c.acquire().unwrap(); - drop((a, b)); -} - -#[test] -fn server_available() { - let c = t!(Client::new(10)); - assert_eq!(c.available().unwrap(), 10); - let a = c.acquire().unwrap(); - assert_eq!(c.available().unwrap(), 9); - drop(a); - assert_eq!(c.available().unwrap(), 10); -} - -#[test] -fn server_none_available() { - let c = t!(Client::new(2)); - assert_eq!(c.available().unwrap(), 2); - let a = c.acquire().unwrap(); - assert_eq!(c.available().unwrap(), 1); - let b = c.acquire().unwrap(); - assert_eq!(c.available().unwrap(), 0); - drop(a); - assert_eq!(c.available().unwrap(), 1); - drop(b); - assert_eq!(c.available().unwrap(), 2); -} - -#[test] -fn server_blocks() { - let c = t!(Client::new(1)); - let a = c.acquire().unwrap(); - let hit = Arc::new(AtomicBool::new(false)); - let hit2 = hit.clone(); - let (tx, rx) = mpsc::channel(); - let t = thread::spawn(move || { - tx.send(()).unwrap(); - let _b = c.acquire().unwrap(); - hit2.store(true, Ordering::SeqCst); - }); - rx.recv().unwrap(); - assert!(!hit.load(Ordering::SeqCst)); - drop(a); - t.join().unwrap(); - assert!(hit.load(Ordering::SeqCst)); -} - -#[test] -fn make_as_a_single_thread_client() { - let c = t!(Client::new(1)); - let td = tempfile::tempdir().unwrap(); - - let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string()); - let mut cmd = Command::new(prog); - cmd.current_dir(td.path()); - - t!(t!(File::create(td.path().join("Makefile"))).write_all( - b" -all: foo bar -foo: -\techo foo -bar: -\techo bar -" - )); - - // The jobserver protocol means that the `make` process itself "runs with a - // token", so we acquire our one token to drain the jobserver, and this - // should mean that `make` itself never has a second token available to it. - let _a = c.acquire(); - c.configure(&mut cmd); - let output = t!(cmd.output()); - println!( - "\n\t=== stderr\n\t\t{}", - String::from_utf8_lossy(&output.stderr).replace("\n", "\n\t\t") - ); - println!( - "\t=== stdout\n\t\t{}", - String::from_utf8_lossy(&output.stdout).replace("\n", "\n\t\t") - ); - - assert!(output.status.success()); - assert!(output.stderr.is_empty()); - - let stdout = String::from_utf8_lossy(&output.stdout).replace("\r\n", "\n"); - let a = "\ -echo foo -foo -echo bar -bar -"; - let b = "\ -echo bar -bar -echo foo -foo -"; - - assert!(stdout == a || stdout == b); -} - -#[test] -fn make_as_a_multi_thread_client() { - let c = t!(Client::new(1)); - let td = tempfile::tempdir().unwrap(); - - let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string()); - let mut cmd = Command::new(prog); - cmd.current_dir(td.path()); - - t!(t!(File::create(td.path().join("Makefile"))).write_all( - b" -all: foo bar -foo: -\techo foo -bar: -\techo bar -" - )); - - // We're leaking one extra token to `make` sort of violating the makefile - // jobserver protocol. It has the desired effect though. - c.configure(&mut cmd); - let output = t!(cmd.output()); - println!( - "\n\t=== stderr\n\t\t{}", - String::from_utf8_lossy(&output.stderr).replace("\n", "\n\t\t") - ); - println!( - "\t=== stdout\n\t\t{}", - String::from_utf8_lossy(&output.stdout).replace("\n", "\n\t\t") - ); - - assert!(output.status.success()); -} - -#[test] -fn zero_client() { - let client = t!(Client::new(0)); - let (tx, rx) = mpsc::channel(); - let helper = client - .into_helper_thread(move |a| drop(tx.send(a))) - .unwrap(); - helper.request_token(); - helper.request_token(); - - for _ in 0..1000 { - assert!(rx.try_recv().is_err()); - } -} +use std::env;
+use std::fs::File;
+use std::io::prelude::*;
+use std::process::Command;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::mpsc;
+use std::sync::Arc;
+use std::thread;
+
+use jobserver::Client;
+
+macro_rules! t {
+ ($e:expr) => {
+ match $e {
+ Ok(e) => e,
+ Err(e) => panic!("{} failed with {}", stringify!($e), e),
+ }
+ };
+}
+
+#[test]
+fn server_smoke() {
+ let c = t!(Client::new(1));
+ drop(c.acquire().unwrap());
+ drop(c.acquire().unwrap());
+}
+
+#[test]
+fn server_multiple() {
+ let c = t!(Client::new(2));
+ let a = c.acquire().unwrap();
+ let b = c.acquire().unwrap();
+ drop((a, b));
+}
+
+#[test]
+fn server_available() {
+ let c = t!(Client::new(10));
+ assert_eq!(c.available().unwrap(), 10);
+ let a = c.acquire().unwrap();
+ assert_eq!(c.available().unwrap(), 9);
+ drop(a);
+ assert_eq!(c.available().unwrap(), 10);
+}
+
+#[test]
+fn server_none_available() {
+ let c = t!(Client::new(2));
+ assert_eq!(c.available().unwrap(), 2);
+ let a = c.acquire().unwrap();
+ assert_eq!(c.available().unwrap(), 1);
+ let b = c.acquire().unwrap();
+ assert_eq!(c.available().unwrap(), 0);
+ drop(a);
+ assert_eq!(c.available().unwrap(), 1);
+ drop(b);
+ assert_eq!(c.available().unwrap(), 2);
+}
+
+#[test]
+fn server_blocks() {
+ let c = t!(Client::new(1));
+ let a = c.acquire().unwrap();
+ let hit = Arc::new(AtomicBool::new(false));
+ let hit2 = hit.clone();
+ let (tx, rx) = mpsc::channel();
+ let t = thread::spawn(move || {
+ tx.send(()).unwrap();
+ let _b = c.acquire().unwrap();
+ hit2.store(true, Ordering::SeqCst);
+ });
+ rx.recv().unwrap();
+ assert!(!hit.load(Ordering::SeqCst));
+ drop(a);
+ t.join().unwrap();
+ assert!(hit.load(Ordering::SeqCst));
+}
+
+#[test]
+fn make_as_a_single_thread_client() {
+ let c = t!(Client::new(1));
+ let td = tempfile::tempdir().unwrap();
+
+ let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string());
+ let mut cmd = Command::new(prog);
+ cmd.current_dir(td.path());
+
+ t!(t!(File::create(td.path().join("Makefile"))).write_all(
+ b"
+all: foo bar
+foo:
+\techo foo
+bar:
+\techo bar
+"
+ ));
+
+ // The jobserver protocol means that the `make` process itself "runs with a
+ // token", so we acquire our one token to drain the jobserver, and this
+ // should mean that `make` itself never has a second token available to it.
+ let _a = c.acquire();
+ c.configure(&mut cmd);
+ let output = t!(cmd.output());
+ println!(
+ "\n\t=== stderr\n\t\t{}",
+ String::from_utf8_lossy(&output.stderr).replace("\n", "\n\t\t")
+ );
+ println!(
+ "\t=== stdout\n\t\t{}",
+ String::from_utf8_lossy(&output.stdout).replace("\n", "\n\t\t")
+ );
+
+ assert!(output.status.success());
+ assert!(output.stderr.is_empty());
+
+ let stdout = String::from_utf8_lossy(&output.stdout).replace("\r\n", "\n");
+ let a = "\
+echo foo
+foo
+echo bar
+bar
+";
+ let b = "\
+echo bar
+bar
+echo foo
+foo
+";
+
+ assert!(stdout == a || stdout == b);
+}
+
+#[test]
+fn make_as_a_multi_thread_client() {
+ let c = t!(Client::new(1));
+ let td = tempfile::tempdir().unwrap();
+
+ let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string());
+ let mut cmd = Command::new(prog);
+ cmd.current_dir(td.path());
+
+ t!(t!(File::create(td.path().join("Makefile"))).write_all(
+ b"
+all: foo bar
+foo:
+\techo foo
+bar:
+\techo bar
+"
+ ));
+
+ // We're leaking one extra token to `make` sort of violating the makefile
+ // jobserver protocol. It has the desired effect though.
+ c.configure(&mut cmd);
+ let output = t!(cmd.output());
+ println!(
+ "\n\t=== stderr\n\t\t{}",
+ String::from_utf8_lossy(&output.stderr).replace("\n", "\n\t\t")
+ );
+ println!(
+ "\t=== stdout\n\t\t{}",
+ String::from_utf8_lossy(&output.stdout).replace("\n", "\n\t\t")
+ );
+
+ assert!(output.status.success());
+}
+
+#[test]
+fn zero_client() {
+ let client = t!(Client::new(0));
+ let (tx, rx) = mpsc::channel();
+ let helper = client
+ .into_helper_thread(move |a| drop(tx.send(a)))
+ .unwrap();
+ helper.request_token();
+ helper.request_token();
+
+ for _ in 0..1000 {
+ assert!(rx.try_recv().is_err());
+ }
+}
|