Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compilepkg: fix race when run without sandbox #3145

Merged
merged 5 commits into from
Jun 4, 2022

Conversation

sluongng
Copy link
Contributor

@sluongng sluongng commented May 9, 2022

compilepkg: fix race when run without sandbox

On platforms without sandbox execution support such as Windows, multiple
go_test targets declared in the same package would cause a race condition.

[
    go_library(
        name = "same_package_{}".format(i),
        srcs = ["same_package.go"],
        importpath = "github.com/bazelbuild/rules_go/tests/core/go_test/same_package_{}".format(i),
        x_defs = {
            "name": "{}".format(i),
        },
    )
    for i in range(1, 80)
]

[
    go_test(
        name = "same_package_{}_test".format(i),
        srcs = ["same_package_test.go"],
        embed = [":same_package_{}".format(i)],
    )
    for i in range(1, 80)
]

For each of go_test targets, there would be internal archive and
external archive that needs to compile from source files. However, due
to testfilter, external archive would end up without any Go source and
thus compilepkg needs to generate a dummy source file _empty.go to
trick 'go tool compile'.

This _empty.go file is generated in the output path of the Bazel
package thus multiple go_test targets in the same package would generate
this file using the same path. On a sandbox supported platform,
this is not a problem as the full path is prefixed with the sandbox root
dir. However, without sandbox, the full path is the same for each
go_test's external archive compilation, leading to a race condition
where multiple compilations running in parallel would compete to
create/delete the same file.

Fix this by using the unique importPath to create a directory to store
the _empty.go file. This would effectively give each go_test compilation
it's own _empty.go file in non-sandbox environment.

Worth to note that in current solution, we still have yet to solve
problem when 'importPath' is not provided. For example, in a setup like
this where go_library does not exist, importPath will be blank and
thus causes similar issues on non-sandbox platforms.

[
    go_test(
        name = "same_package_{}_test".format(i),
        srcs = ["same_package_test.go"],
    )
    for i in range(1, 80)
]

Although this is a valid use case, we recommend avoid setting up tests
this way. Instead, you could setup a single go_test target with multiple
shards to achieve similar result:

go_test(
    name = "same_package_{}_test".format(i),
    srcs = ["same_package_test.go"],
    shard_count = 80,
)

For more information, please review Bazel Test Sharding documentation(1)

(1): https://docs.bazel.build/versions/main/test-encyclopedia.html#test-sharding

Fix #3144

@sluongng sluongng force-pushed the sluongng/fix-window-issue branch 6 times, most recently from a2b9cb7 to 80312f5 Compare May 10, 2022 09:31
@sluongng sluongng force-pushed the sluongng/fix-window-issue branch 7 times, most recently from 8d25896 to 7e75b42 Compare May 10, 2022 14:50
@sluongng sluongng changed the title Fix window CI when multiple go_library is declared in same directory compilepkg: fix race when run without sandbox May 10, 2022
@sluongng sluongng marked this pull request as ready for review May 10, 2022 15:01
@sluongng sluongng force-pushed the sluongng/fix-window-issue branch 7 times, most recently from e1a17c4 to 8e809f5 Compare May 11, 2022 08:48
@sluongng
Copy link
Contributor Author

This PR is ready for review.

The detail contexts is shared in #3144

@sluongng
Copy link
Contributor Author

Merged latest master into this PR to resolve the upstream merge-conflict

@sluongng
Copy link
Contributor Author

@achew22 @linzhp could you guys please take a look at this PR?

The fix is relatively small and there are tests included.

Copy link
Contributor

@linzhp linzhp left a comment

Choose a reason for hiding this comment

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

Thanks for the fix. It also make the Windows CI on rules_go more stable. Minor suggestions

go/tools/builders/compilepkg.go Show resolved Hide resolved
go/tools/builders/compilepkg.go Outdated Show resolved Hide resolved
@linzhp linzhp self-assigned this May 25, 2022
On platforms without sandbox execution support such as Windows, multiple
go_test targets declared in the same package would cause a race condition.

```
[
    go_library(
        name = "same_package_{}".format(i),
        srcs = ["same_package.go"],
        importpath = "github.com/bazelbuild/rules_go/tests/core/go_test/same_package_{}".format(i),
        x_defs = {
            "name": "{}".format(i),
        },
    )
    for i in range(1, 80)
]

[
    go_test(
        name = "same_package_{}_test".format(i),
        srcs = ["same_package_test.go"],
        embed = [":same_package_{}".format(i)],
    )
    for i in range(1, 80)
]
```

For each of go_test targets, there would be internal archive and
external archive that needs to compile from source files.  However, due
to testfilter, external archive would end up without any Go source and
thus compilepkg needs to generate a dummy source file `_empty.go` to
trick 'go tool compile'.

This `_empty.go` file is generated in the output path of the Bazel
package thus multiple go_test targets in the same package would generate
this file using the same path.  On a sandbox supported platform,
this is not a problem as the full path is prefixed with the sandbox root
dir.  However, without sandbox, the full path is the same for each
go_test's external archive compilation, leading to a race condition
where multiple compilations running in parallel would compete to
create/delete the same file.

Fix this by using the unique importPath to create a directory to store
the _empty.go file.  This would effectively give each go_test compilation
it's own _empty.go file in non-sandbox environment.

Worth to note that in current solution, we still have yet to solve
problem when 'importPath' is not provided. For example, in a setup like
this where `go_library` does not exist, `importPath` will be blank and
thus causes similar issues on non-sandbox platforms.

```
[
    go_test(
        name = "same_package_{}_test".format(i),
        srcs = ["same_package_test.go"],
    )
    for i in range(1, 80)
]
```

Although this is a valid use case, we recommend avoid setting up tests
this way. Instead, you could setup a single go_test target with multiple
shards to achieve similar result:

```
go_test(
    name = "same_package_{}_test".format(i),
    srcs = ["same_package_test.go"],
    shard_count = 80,
)
```

For more information, please review Bazel Test Sharding documentation(1)

(1): https://docs.bazel.build/versions/main/test-encyclopedia.html#test-sharding
Adding a handy test_suite to run all the generated tests easily during
development.
Make use of the unique label's name constraints instead of relying on
import path, which may be empty for packages with test only.
@sluongng
Copy link
Contributor Author

Looks like all CI have passed @linzhp PTAL 🙏

Copy link
Collaborator

@fmeum fmeum left a comment

Choose a reason for hiding this comment

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

@linzhp I don't have write access yet, could you do the merge if you are satisfied with the PR?

@linzhp linzhp merged commit 1e9931b into bazelbuild:master Jun 4, 2022
@fmeum fmeum mentioned this pull request Jun 5, 2022
gcf-merge-on-green bot referenced this pull request in googleapis/gapic-generator-go Jun 6, 2022
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [io_bazel_rules_go](https://github.com/bazelbuild/rules_go) | http_archive | minor | `v0.32.0` -> `v0.33.0` |

---

### Release Notes

<details>
<summary>bazelbuild/rules_go</summary>

### [`v0.33.0`](https://github.com/bazelbuild/rules_go/releases/tag/v0.33.0)

[Compare Source](https://github.com/bazelbuild/rules_go/compare/v0.32.0...v0.33.0)

##### Breaking changes

-   [cover](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#cover) has been removed in v0.32.0. Please [leave a comment](https://github.com/bazelbuild/rules_go/issues/3168) if you are affected by this.

##### Deprecations

-   The [asm](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#asm), [compile](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#compile), and [pack](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#pack) action generators provided by [go_context](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#go_context) are deprecated and planned for removal in version v0.36.0. Please leave a comment on the [tracking bug](https://github.com/bazelbuild/rules_go/issues/3168) if [archive](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#archive) and [link](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#link) are not suitable replacements for your use cases.

##### Bug Fixes

-   [@&#8203;sluongng](https://github.com/sluongng) fixed a race condition that could cause non-sandboxed builds of `go_test` targets to fail ([https://github.com/bazelbuild/rules_go/pull/3145](https://github.com/bazelbuild/rules_go/pull/3145))
-   [@&#8203;abhinav](https://github.com/abhinav) made `//go:embed` work with `go_path` ([https://github.com/bazelbuild/rules_go/pull/3163](https://github.com/bazelbuild/rules_go/pull/3163))
-   [@&#8203;xytan0056](https://github.com/xytan0056) made gopackagesdriver work with Go 1.18 ([https://github.com/bazelbuild/rules_go/pull/3157](https://github.com/bazelbuild/rules_go/pull/3157))
-   [@&#8203;nickgooding](https://github.com/nickgooding) ensured that `gomock` can be used with any Gazelle naming convention ([https://github.com/bazelbuild/rules_go/pull/3155](https://github.com/bazelbuild/rules_go/pull/3155))
-   `go_library` targets using CGo can now reference unresolved symbols ([https://github.com/bazelbuild/rules_go/pull/3174](https://github.com/bazelbuild/rules_go/pull/3174))

Thanks to all of the contributors!

##### Compatibility

The minimum required version of Bazel remains at 4.2.1.

##### Updated dependencies

-   Updated `org_golang_x_sys`, `org_golang_x_xerrors`, `org_golang_google_genproto`, `go_googleapis` to their most recent commit as of 2022-06-05

As always, you can use higher versions of rules_go's dependencies by declaring them in WORKSPACE before calling go_rules_dependencies. Lower versions may work but are not supported.

##### `WORKSPACE` code

    load("@&#8203;bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

    http_archive(
        name = "io_bazel_rules_go",
        sha256 = "685052b498b6ddfe562ca7a97736741d87916fe536623afb7da2824c0211c369",
        urls = [
            "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.33.0/rules_go-v0.33.0.zip",
            "https://github.com/bazelbuild/rules_go/releases/download/v0.33.0/rules_go-v0.33.0.zip",
        ],
    )

    load("@&#8203;io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")

    go_rules_dependencies()

    go_register_toolchains(version = "1.18.3")

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, click this checkbox.

---

This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/gapic-generator-go).
gcf-merge-on-green bot referenced this pull request in googleapis/gapic-config-validator Jun 6, 2022
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [io_bazel_rules_go](https://github.com/bazelbuild/rules_go) | http_archive | minor | `v0.32.0` -> `v0.33.0` |

---

### Release Notes

<details>
<summary>bazelbuild/rules_go</summary>

### [`v0.33.0`](https://github.com/bazelbuild/rules_go/releases/tag/v0.33.0)

[Compare Source](https://github.com/bazelbuild/rules_go/compare/v0.32.0...v0.33.0)

#### Breaking changes

-   [cover](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#cover) has been removed in v0.32.0. Please [leave a comment](https://github.com/bazelbuild/rules_go/issues/3168) if you are affected by this.

#### Deprecations

-   The [asm](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#asm), [compile](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#compile), and [pack](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#pack) action generators provided by [go_context](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#go_context) are deprecated and planned for removal in version v0.36.0. Please leave a comment on the [tracking bug](https://github.com/bazelbuild/rules_go/issues/3168) if [archive](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#archive) and [link](https://github.com/bazelbuild/rules_go/blob/master/go/toolchains.rst#link) are not suitable replacements for your use cases.

#### Bug Fixes

-   [@&#8203;sluongng](https://github.com/sluongng) fixed a race condition that could cause non-sandboxed builds of `go_test` targets to fail ([https://github.com/bazelbuild/rules_go/pull/3145](https://github.com/bazelbuild/rules_go/pull/3145))
-   [@&#8203;abhinav](https://github.com/abhinav) made `//go:embed` work with `go_path` ([https://github.com/bazelbuild/rules_go/pull/3163](https://github.com/bazelbuild/rules_go/pull/3163))
-   [@&#8203;xytan0056](https://github.com/xytan0056) made gopackagesdriver work with Go 1.18 ([https://github.com/bazelbuild/rules_go/pull/3157](https://github.com/bazelbuild/rules_go/pull/3157))
-   [@&#8203;nickgooding](https://github.com/nickgooding) ensured that `gomock` can be used with any Gazelle naming convention ([https://github.com/bazelbuild/rules_go/pull/3155](https://github.com/bazelbuild/rules_go/pull/3155))
-   `go_library` targets using CGo can now reference unresolved symbols ([https://github.com/bazelbuild/rules_go/pull/3174](https://github.com/bazelbuild/rules_go/pull/3174))

Thanks to all of the contributors!

#### Compatibility

The minimum required version of Bazel remains at 4.2.1.

#### Updated dependencies

-   Updated `org_golang_x_sys`, `org_golang_x_xerrors`, `org_golang_google_genproto`, `go_googleapis` to their most recent commit as of 2022-06-05

As always, you can use higher versions of rules_go's dependencies by declaring them in WORKSPACE before calling go_rules_dependencies. Lower versions may work but are not supported.

#### `WORKSPACE` code

    load("@&#8203;bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

    http_archive(
        name = "io_bazel_rules_go",
        sha256 = "685052b498b6ddfe562ca7a97736741d87916fe536623afb7da2824c0211c369",
        urls = [
            "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.33.0/rules_go-v0.33.0.zip",
            "https://github.com/bazelbuild/rules_go/releases/download/v0.33.0/rules_go-v0.33.0.zip",
        ],
    )

    load("@&#8203;io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")

    go_rules_dependencies()

    go_register_toolchains(version = "1.18.3")

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, click this checkbox.

---

This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/gapic-config-validator).
@sluongng
Copy link
Contributor Author

sluongng commented Jun 8, 2022

FWIW, I think using target's name in the action input might be an anti pattern.
It will become part of the key calculated in action cache, so similar actions would not re-use cached action if they have different names:

[
    go_test(
        name = "test_{}".format(i),
        srcs = ["my_test.go"],
    )
    for i in range(1, 80)
]

In such (rare?) case, the 80 test binary compilation/link might run not once, but 80 times.

I am convinced that this is not an issue worth solving right now 🤔
The use case is very rare that it might not matter at all.

But thinking about the (unrelated) use case of constructing a synthetic tests for RBE, where I want the build graph to be widen to benefit from parallelism, made me think back about this PR and how it might have unintentionally enabled such use case.

@sluongng sluongng deleted the sluongng/fix-window-issue branch June 8, 2022 05:18
@fmeum
Copy link
Collaborator

fmeum commented Jun 8, 2022

Avoiding this would be harder than expected though: The command line is part of the action key computation and necessarily contains the path of the output of the compile/link actions, which is already derived from the test name. If I'm not missing anything, the recent change thus should make a difference here.

@sluongng
Copy link
Contributor Author

sluongng commented Jun 8, 2022

I quickly created a test like this:

bazel aquery '//tests/core/go_test:same_package_1_test' > /tmp/same_package_1.txt
bazel aquery '//tests/core/go_test:same_package_2_test' > /tmp/same_package_2.txt

diff -u /tmp/same_package_*

# less noisy
diff -u /tmp/same_package_* | grep -vE '(1|2)_test'

Then I ran the test before and after reverting this commit.

# check out my commit
> git checkout 1e9931b0c2ec4c89f6f651dc9cfccda75d95e1d5

# run test

# revert //go dir
> git checkout 1e9931b0c2ec4c89f6f651dc9cfccda75d95e1d5^ -- go
> git status
HEAD detached at 1e9931b0
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   go/private/actions/compilepkg.bzl
        modified:   go/tools/builders/compilepkg.go

And the result seems to be that ActionKey values were always updated as target's name has always been part of the Command Line input.

I event tried doing this on top of my patch

master ~/work/misc/rules_go> git diff
diff --git a/tests/core/go_test/BUILD.bazel b/tests/core/go_test/BUILD.bazel
index 829ca8d5..3e58db06 100644
--- a/tests/core/go_test/BUILD.bazel
+++ b/tests/core/go_test/BUILD.bazel
@@ -199,6 +199,7 @@ go_library(
     go_test(
         name = "same_package_{}_test".format(i),
         srcs = ["same_package_test.go"],
+        importpath = "blah/blah",
     )
     for i in range(1, 80)
 ]

And then ran the same test: same result, ActionKey hash always changed.


So it's safe to conclude that this PR did not introduce a regression. 😇

I was being paranoid for no reason. Sorry for the noise 🙇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

window: race condition when declare multiple go_library in the same directory
3 participants