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

Unify the task source and task canceller API #21804

Merged
merged 2 commits into from Nov 16, 2018

Conversation

@AgustinCB
Copy link
Contributor

AgustinCB commented Sep 24, 2018

To do so, I created a struct TaskManagement(TaskSource, TaskCanceller) and made *_task_source return that instead of just
the task source.

Next, I refactored all places in which task_canceller by basically
removing them in favour of a previously called *_task_source.

I tried to make task_canceller a private method in Window, with the
hope of enforcing the use of *_task_source. However, it's used in
components/script/dom/globalscope.rs:575 in such a way that will make it
harder to avoid. I decided to leave it that way.

It'd be possible to unify *_task_source in such a way that we would
have only one method. However, I decided not to do it because one of the
TaskSource implementations is special:
history_traversal_task_source. Not wanting to over complicate things,
I decided to leave the structure this way.


  • ./mach build -d does not report any errors

  • ./mach test-tidy does not report any errors

  • These changes fix #21154 (github issue number if applicable).

  • These changes do not require tests because it's refactoring code that should already be tested.


This change is Reviewable

@highfive
Copy link

highfive commented Sep 24, 2018

Heads up! This PR modifies the following files:

  • @asajeffrey: components/script/dom/textcontrol.rs, components/script/layout_image.rs, components/script/dom/audioscheduledsourcenode.rs, components/script/dom/htmlscriptelement.rs, components/script/dom/baseaudiocontext.rs and 17 more
  • @KiChjang: components/script/dom/textcontrol.rs, components/script/layout_image.rs, components/script/dom/audioscheduledsourcenode.rs, components/script/dom/htmlscriptelement.rs, components/script/dom/baseaudiocontext.rs and 17 more
@highfive
Copy link

highfive commented Sep 24, 2018

warning Warning warning

  • These commits modify script code, but no tests are modified. Please consider adding a test!
@paulrouget paulrouget removed their assignment Sep 25, 2018
@gterzian
Copy link
Member

gterzian commented Sep 26, 2018

Thanks! I will look at this later this week!

@gterzian
Copy link
Member

gterzian commented Sep 26, 2018

Is it just me, or is the HistoryTraversalTaskSource not actually used anywhere(anymore)? It seems that History communicates directly via the constellation, and the constellation will "queue" a task to update or remove the history state on a script-thread via a message back to it? This seems to match the concept of a https://html.spec.whatwg.org/multipage/history.html#session-history-traversal-queue, which is tied to a specific 'session history event loop', which runs "in parallel" to the script event-loop to which the other task sources are tied.

Should we perhaps remove the HistoryTraversalTaskSource?

cc @cbrewster @jdm

Copy link
Member

gterzian left a comment

Looks like a very good step in the right direction. I have one question on the overall design...

@@ -1207,6 +1214,8 @@ impl WindowMethods for Window {
}
}

pub struct TaskManagement<T: TaskSource>(pub T, pub TaskCanceller);

This comment has been minimized.

Copy link
@gterzian

gterzian Sep 26, 2018

Member

It'd be possible to unify *_task_source in such a way that we would
have only one method.

Have you considered making this a struct that would completely take over the task management from a Window?

Perhaps something like:

struct TaskManager {
    task_cancellers: DomRefCell<HashMap<TaskSourceName, Arc<AtomicBool>>>,
    task_sources: DomRefCell<HashMap<TaskSourceName, Box<TaskSource>>>
}

impl TaskManager {
    fn new(task_sources: HashMap<TaskSourceName, Box<TaskSource>>) -> Self;
    fn task_source_with_canceller(&self, name: TaskSourceName) -> (Box<TaskSource>, TaskCanceller);
    fn task_source(&self, name: TaskSourceName)-> Box<TaskSource>;
    fn task_canceller(&self, name: TaskSourceName)-> TaskCanceller;
}

Perhaps it could be initialized when a new page loads, passed to Window::new, and basically replace all the task source related methods and fields on Window with one public task_manager field?

one of the
TaskSource implementations is special:
history_traversal_task_source. Not wanting to over complicate things,
I decided to leave the structure this way.

Perhaps if you were to implement TaskSource for HistoryTraversalTaskSource(or remove it if others confirm this is a good idea, see my other comment), it could work without requiring too much complication?

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Sep 26, 2018

Author Contributor

I thought of that, but not knowing what to do with HistoryTraversalTaskSource made me chose something simpler. Truth being, I'd love to move responsibilities outside of Window (it's the most overwhelming file in the source code) :). Your proposal of adding a "fake" implementation for HistoryTraversalTaskSource sounds good to me and addresses the concern I had. Will address your feedback and push the new version in the next days.

Thanks for the suggestion.

This comment has been minimized.

Copy link
@gterzian

gterzian Sep 26, 2018

Member

Sounds good, you could also do an actually 'real' implementation, since we might actually need it later for #21815

I think basically a copy/paste from any of the other task-source, and adding a new ScriptThreadEventCategory, should do the trick...

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Sep 27, 2018

Author Contributor

Unfortunately, TaskSource can't be made a trait object :(.

I do have the HistoryTraversalTaskSource implementation. Should I just do that? Or maybe we can somehow modify TaskSource to allow it to make a trait object?

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Sep 27, 2018

Author Contributor

I guess I could also make a less elegant...

struct TaskManager {
    task_cancellers: DomRefCell<HashMap<TaskSourceName, Arc<AtomicBool>>>,
    *_task_sources: DomRefCell<*TaskSource>,
}

Although we would still have a lot of methods.

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Sep 28, 2018

Author Contributor

Of course! Here it's:

error[E0038]: the trait `task_source::TaskSource` cannot be made into an object
   --> components/script/task_manager.rs:119:5
    |
119 |     pub fn task_source(&self, name: TaskSourceName) -> Box<TaskSource> {
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `task_source::TaskSource` cannot be made into an object
    |
    = note: method `queue_with_canceller` has generic type parameters
    = note: method `queue` has generic type parameters
    = note: the trait cannot contain associated consts like `NAME

This comment has been minimized.

Copy link
@gterzian

gterzian Sep 28, 2018

Member

I see, so that's not going to work :)

I haven't found another way to avoid the repetition of methods, have you?

In any case, introducing a TaskManager that will lighten up Window on methods and fields is probably progress.

Thanks for pushing to additional commits, I will review them as is unless we come up with a better design...

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Sep 28, 2018

Author Contributor

I tried to trick the compiler in a couple naive ways (i.e. using & mut instead of Box, wrapping the TaskSource in a structure that is object safe, etc.). As expected, the compiler outsmarted me.

Another thing I considered was just living with the duplication, but create a couple macros so it's less apparent. I didn't want to go through that route, because I didn't see any macro in the rest of the code and I know some projects are (rightfully) cautious about them. If this were a personal project, though, and the choice depended only on me, I'd probably do that.

This comment has been minimized.

Copy link
@jdm

jdm Sep 28, 2018

Member

We do use macros when it makes sense in Servo's codebase.

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Sep 29, 2018

Author Contributor

Good to know! Will push (yet another) commit with how it would look like with macros. Thanks!

@cbrewster
Copy link
Member

cbrewster commented Sep 26, 2018

We don’t use the history task source, I tried using it so we would line up with the spec a bit more, but it ends up queuing a message on the task source to send a message to the constellation, which doesn’t make much sense. I would be okay with removing the history task source, but it really depends on how much we care about history spec compliance.

@gterzian
Copy link
Member

gterzian commented Sep 26, 2018

@cbrewster Thanks for the info!

I'll open a separate issue to discuss this...

@AgustinCB I think in the context of this PR, we could implement a blank TaskSource for HistoryTraversalTaskSource that always returns Ok(()) just so that it can be used along with all the other task sources as part of the 'task management'.

EDIT: I think an actual task source implementation similar to the others is probably better, since I just realized the history-traversal task queue and task source aren't completely related, see #21815

Could please do this implementation of TaskSource for HistoryTraversalTaskSource in a separate commit from the rest of this PR, yet include it in this one(using the task-source as mentioned in the issue is probably out of scope of this PR, yet could be done in another)?

@ghost
Copy link

ghost commented Sep 28, 2018

No Taskcluster jobs started for this pull request
The `allowPullRequests` configuration for this repository (in `.taskcluster.yml` on the
default branch) does not allow starting tasks for this pull request.
@AgustinCB AgustinCB force-pushed the AgustinCB:unify-task-source-canceller-api branch from fe67621 to abe1a44 Sep 28, 2018
@ghost
Copy link

ghost commented Sep 28, 2018

No Taskcluster jobs started for this pull request
The `allowPullRequests` configuration for this repository (in `.taskcluster.yml` on the
default branch) does not allow starting tasks for this pull request.
@AgustinCB AgustinCB force-pushed the AgustinCB:unify-task-source-canceller-api branch from abe1a44 to abdcefa Sep 28, 2018
Copy link
Member

gterzian left a comment

Looks great! Just a few nits really

@@ -137,7 +137,7 @@ impl AudioContextMethods for AudioContext {
if base_context.State() != AudioContextState::Suspended {
base_context.set_state_attribute(AudioContextState::Suspended);
let window = DomRoot::downcast::<Window>(context.global()).unwrap();
window.dom_manipulation_task_source().queue_simple_event(
window.task_manager().dom_manipulation_task_source().queue_simple_event(

This comment has been minimized.

Copy link
@gterzian

gterzian Sep 29, 2018

Member

nit: I'm wondering if task_source could be used instead here.

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Sep 29, 2018

Author Contributor

Not really, since task_source is borrowed in line 131.

@@ -199,7 +199,7 @@ impl AudioContextMethods for AudioContext {
if base_context.State() != AudioContextState::Closed {
base_context.set_state_attribute(AudioContextState::Closed);
let window = DomRoot::downcast::<Window>(context.global()).unwrap();
window.dom_manipulation_task_source().queue_simple_event(
window.task_manager().dom_manipulation_task_source().queue_simple_event(

This comment has been minimized.

Copy link
@gterzian

gterzian Sep 29, 2018

Member

same here

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Sep 29, 2018

Author Contributor

In here, it's borrowed in line 193.

let callback = OnEndedCallback::new(move || {
let _ = task_source.queue_with_canceller(
task!(ended: move || {
let this = this.root();
let global = this.global();
let window = global.as_window();
window.dom_manipulation_task_source().queue_simple_event(
window.task_manager().dom_manipulation_task_source().queue_simple_event(

This comment has been minimized.

Copy link
@gterzian

gterzian Sep 29, 2018

Member

same here

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Sep 29, 2018

Author Contributor

Similar, is borrowed before.

@@ -225,7 +225,7 @@ impl BaseAudioContext {
if this.state.get() != AudioContextState::Running {
this.state.set(AudioContextState::Running);
let window = DomRoot::downcast::<Window>(this.global()).unwrap();
window.dom_manipulation_task_source().queue_simple_event(
window.task_manager().dom_manipulation_task_source().queue_simple_event(

This comment has been minimized.

Copy link
@gterzian

gterzian Sep 29, 2018

Member

same here

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Sep 29, 2018

Author Contributor

It's also borrowed before.

To be fair, this parts also bothered me. I suspect that there's some way in which this code can be refactor to call less clones. But I'm not sure it's immediately obvious.

Maybe I can offer you to open an issue to track the tech debt?

This comment has been minimized.

Copy link
@gterzian

gterzian Oct 1, 2018

Member

Ok, thanks for the info. I'm not sure at this point how to make a potential issue actionable, so I will not open one. If you happen to have a clearer idea, it could be useful if you were to open one indeed...

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Oct 1, 2018

Author Contributor

No, not really.

win.dom_manipulation_task_source()
.queue_simple_event(target, atom!("close"), &win);
win.task_manager().dom_manipulation_task_source()
.queue_simple_event(target, atom!("close"), &win);

This comment has been minimized.

Copy link
@gterzian

gterzian Sep 29, 2018

Member

nit: This is what rustfmt did with it(actually it appears the indent here is slightly different, the idea is 4 space ident I believe, starting at win.task_manager()):

// Step 5
        win.task_manager()
            .dom_manipulation_task_source()
            .queue_simple_event(target, atom!("close"), &win);
@@ -588,11 +589,11 @@ impl HTMLMediaElement {

let context = Arc::new(Mutex::new(HTMLMediaElementContext::new(self)));
let (action_sender, action_receiver) = ipc::channel().unwrap();
let window = window_from_node(self);
let (task_source, canceller) = document.window().task_manager().networking_task_source_with_canceller();

This comment has been minimized.

Copy link
@gterzian

gterzian Sep 29, 2018

Member

nit: this one looks a little too long(you can check with ./mach test-tidy)

self.task_canceller(TaskSourceName::DOMManipulation)
.wrap_task(task),
self.task_manager.task_canceller(TaskSourceName::DOMManipulation)
.wrap_task(task),

This comment has been minimized.

Copy link
@gterzian

gterzian Sep 29, 2018

Member

nit: looks like too much indent like above

self.task_canceller(TaskSourceName::DOMManipulation)
.wrap_task(task),
self.task_manager.task_canceller(TaskSourceName::DOMManipulation)
.wrap_task(task),

This comment has been minimized.

Copy link
@gterzian

gterzian Sep 29, 2018

Member

nit: too much indent like above

@@ -0,0 +1,130 @@
/* This Source Code Form is subject to the terms of the Mozilla Public

This comment has been minimized.

Copy link
@gterzian

gterzian Sep 29, 2018

Member

Since this is a completely new file, you can safely run ./mach rustfmt -d components/script/task_manager.rs and apply the changes to the commit (requires installing first if you haven't, see https://github.com/rust-lang-nursery/rustfmt)

T: TaskOnce + 'static,
{
let msg = MainThreadScriptMsg::Common(CommonScriptMsg::Task(
ScriptThreadEventCategory::DocumentEvent,

This comment has been minimized.

Copy link
@gterzian

gterzian Sep 29, 2018

Member

I think you want to add a new ScriptThreadEventCategory::HistoryEvent(in the same commit)

@AgustinCB AgustinCB force-pushed the AgustinCB:unify-task-source-canceller-api branch from 4ebc260 to c8268f6 Sep 29, 2018
@gterzian
Copy link
Member

gterzian commented Oct 1, 2018

Ok, looks good to me. I think you can squash it into two commits, one fro the history task source changes, and one for the rest. Thanks for tackling this!

@jdm r?

Copy link
Member

jdm left a comment

I agree with all of gterzian's earlier comments, so thank you for addressing them. Nice work on this!

@@ -173,21 +165,7 @@ pub struct Window {
#[ignore_malloc_size_of = "trait objects are hard"]
script_chan: MainThreadScriptChan,
#[ignore_malloc_size_of = "task sources are hard"]

This comment has been minimized.

Copy link
@jdm

jdm Oct 1, 2018

Member

Let's remove this annotation and add any required annotations inside of TaskManager instead.

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Oct 2, 2018

Author Contributor

Sorry, I'm blocked in this I dunno how to get past: What do I have to do to have ignore_malloc_size_of in the TaskManager? I keep getting this error:

error[E0658]: The attribute `ignore_malloc_size_of` is currently unknown to the compiler and may have meaning added to it in the future (see issue #29642)
  --> components/script/task_manager.rs:50:5
   |
50 |     #[ignore_malloc_size_of = "task sources are hard"]
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: add #![feature(custom_attribute)] to the crate attributes to enable

And I don't know how to solve it (I already tried the suggestion).

Thanks!

This comment has been minimized.

Copy link
@jdm

jdm Oct 2, 2018

Member

Did you add #[derive(MallocSizeOf)] to the TaskManager struct as well?

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Oct 3, 2018

Author Contributor

That work, thanks!

@AgustinCB AgustinCB force-pushed the AgustinCB:unify-task-source-canceller-api branch from c8268f6 to 5954474 Oct 3, 2018
@jdm
Copy link
Member

jdm commented Oct 3, 2018

@bors-servo
Copy link
Contributor

bors-servo commented Oct 3, 2018

📌 Commit 5954474 has been approved by jdm

Copy link
Member

gterzian left a comment

I think the problem we've experienced where what was previously an intermittent problem suddenly failed consistently, with tasks not being cancelled when clear_js_runtime has been called, is because despite the latest changes in this PR, that function(via ignore_all_events) is still using the ignore_further_async_events field of Window, which isn't tied to scheduled tasks anymore, since those are now schedule via the TaskManager which has it's own task_cancellers: DomRefCell<HashMap<TaskSourceName, Arc<AtomicBool>>>.

Fixing the above should put the situation back to the previous 'intermittent test failure'...

@@ -1227,14 +1169,6 @@ impl Window {
self.paint_worklet.or_init(|| self.new_paint_worklet())
}

pub fn task_canceller(&self, name: TaskSourceName) -> TaskCanceller {
let mut flags = self.ignore_further_async_events.borrow_mut();

This comment has been minimized.

Copy link
@gterzian

gterzian Nov 6, 2018

Member

The ignore_further_async_events field of Window is still used in a few places, such as ignore_all_events, this makes the task cancelling inside ignore_all_events not have effect on tasks that have been scheduled via the new TaskManager, since the Arc<AtomicBool> are not shared between ignore_further_async_events and TaskManager.task_cancellers.

So we need to remove ignore_further_async_events from Window, and make methods like Window.ignore_all_events used the TaskManager instead.

This comment has been minimized.

Copy link
@AgustinCB

AgustinCB Nov 6, 2018

Author Contributor

Wow, there's no way I'd have seen that. Thank you so much.

One question, before I implement that, is there an (easy) way to properly fix this? I.e. no failure at all. Or would it fall outside the scope of this pr?

This comment has been minimized.

Copy link
@gterzian

gterzian Nov 6, 2018

Member

You're welcome.

I'm not sure if there is an easy way to fix the intermittent failure, although once the above is fixed in one commit, you could re-try, in a separate commit, the suggestion I made at #21804 (comment) (although since it is intermittent, you might have a hard time actually knowing for sure whether it helped or not).

@bors-servo
Copy link
Contributor

bors-servo commented Nov 6, 2018

The latest upstream changes (presumably #22086) made this pull request unmergeable. Please resolve the merge conflicts.

And remove the method in window that returns it, because it isn't used
so far.
@AgustinCB AgustinCB force-pushed the AgustinCB:unify-task-source-canceller-api branch from 5079c12 to 3fb8d4d Nov 13, 2018
@AgustinCB
Copy link
Contributor Author

AgustinCB commented Nov 13, 2018

Arf, this is finally fixed.

Sorry for taking so long with it.

@jdm
Copy link
Member

jdm commented Nov 13, 2018

@bors-servo try=wpt

@bors-servo
Copy link
Contributor

bors-servo commented Nov 13, 2018

Trying commit 3fb8d4d with merge 8b47358...

bors-servo added a commit that referenced this pull request Nov 13, 2018
…try>

Unify the task source and task canceller API

To do so, I created a struct `TaskManagement(TaskSource,
TaskCanceller)` and made `*_task_source` return that instead of just
the task source.

Next, I refactored all places in which `task_canceller` by basically
removing them in favour of a previously called `*_task_source`.

I tried to make `task_canceller` a private method in `Window`, with the
hope of enforcing the use of `*_task_source`. However, it's used in
components/script/dom/globalscope.rs:575 in such a way that will make it
harder to avoid. I decided to leave it that way.

It'd be possible to unify `*_task_source` in such a way that we would
have only one method. However, I decided not to do it because one of the
`TaskSource` implementations is special:
`history_traversal_task_source`. Not wanting to over complicate things,
I decided to leave the structure this way.

---
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix #21154 (github issue number if applicable).

- [x] These changes do not require tests because it's refactoring code that should already be tested.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/21804)
<!-- Reviewable:end -->
@bors-servo
Copy link
Contributor

bors-servo commented Nov 13, 2018

☀️ Test successful - linux-rel-css, linux-rel-wpt
State: approved= try=True

@CYBAI
Copy link
Collaborator

CYBAI commented Nov 14, 2018

Some tidy errors from rustfmt in Travis. You can run ./mach test-tidy to see these errors and fix them with ./mach fmt!

I moved away from the `Window` struct all the logic to handle task
sources, into a new struct called `TaskManager`. In a happy world, I'd
be able to just have there two functions, of the types:

```rust
fn task_source<T: TaskSource>(&self, name: TaskSourceName) -> Box<T>
fn task_source_with_canceller<T: TaskSource>(&self, name: TaskSourceName)
  -> (Box<T>, TaskSourceCanceller)
```

And not so much duplicated code. However, because TaskSource can't be a
trait object (because it has generic type parameters), that's not
possible. Instead, I decided to reduce duplicated logic through macros.

For reasons[1], I have to pass both the name of the function with
canceller and the name of the function without, as I'm not able to
concatenate them in the macro itself. I could probably use
`concat_idents` to create both types already defined and reduce the
amount of arguments by one, but that macro is nightly only. At the same
time, not being able to declare macros inside `impl` forces me to pass
`self` as an argument.

All this makes this solution more verbose than it would be ideally. It
does reduce duplication, but it doesn't reduce the size of the file.

[1](rust-lang/rust#29599)
@AgustinCB AgustinCB force-pushed the AgustinCB:unify-task-source-canceller-api branch from 3fb8d4d to 75eb94a Nov 14, 2018
@AgustinCB
Copy link
Contributor Author

AgustinCB commented Nov 14, 2018

Thanks!

I addressed both issues and rebased.

@jdm
Copy link
Member

jdm commented Nov 16, 2018

@bors-servo r=gterzian

@bors-servo
Copy link
Contributor

bors-servo commented Nov 16, 2018

📌 Commit 75eb94a has been approved by gterzian

@bors-servo
Copy link
Contributor

bors-servo commented Nov 16, 2018

Testing commit 75eb94a with merge a2937d9...

bors-servo added a commit that referenced this pull request Nov 16, 2018
…terzian

Unify the task source and task canceller API

To do so, I created a struct `TaskManagement(TaskSource,
TaskCanceller)` and made `*_task_source` return that instead of just
the task source.

Next, I refactored all places in which `task_canceller` by basically
removing them in favour of a previously called `*_task_source`.

I tried to make `task_canceller` a private method in `Window`, with the
hope of enforcing the use of `*_task_source`. However, it's used in
components/script/dom/globalscope.rs:575 in such a way that will make it
harder to avoid. I decided to leave it that way.

It'd be possible to unify `*_task_source` in such a way that we would
have only one method. However, I decided not to do it because one of the
`TaskSource` implementations is special:
`history_traversal_task_source`. Not wanting to over complicate things,
I decided to leave the structure this way.

---
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix #21154 (github issue number if applicable).

- [x] These changes do not require tests because it's refactoring code that should already be tested.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/21804)
<!-- Reviewable:end -->
@bors-servo bors-servo merged commit 75eb94a into servo:master Nov 16, 2018
2 of 3 checks passed
2 of 3 checks passed
continuous-integration/appveyor/pr AppVeyor build failed
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
homu Test successful
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

8 participants
You can’t perform that action at this time.