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

Share the stack amongst CLI plugins instead of cloning it #11288

Closed
wants to merge 1 commit into from

Conversation

rtpg
Copy link
Contributor

@rtpg rtpg commented Dec 11, 2023

How to experience a huge slowdown on your machine:

let $stuff = lsof | from ssv -m 1

(This is routinely a 6-digit-length table)

After doing this, at least on my pretty beefy machine prompts take about 3 or 4 seconds to load instead of ~instant.
This shows up in --log-level info. This shows up in perf as a lot of activity on Value::Clone in particualr.

Stack clones end up copying all the values inside of enviornment variables, which can be super costly if we have very large variables inside of the environment. Instead, we can pay some small costs to lock the environment (at least that's the theory), and share all of this.

I believe there's probably some subtle bugs that were present in main do to cloning of the stack instead of sharing (given that we were cloning to get a mutable copy, after all). I did not really try to find such bugs though, as here I mainly wanted to grasp where the initial performance issue was.

After this patch, the performance issue goes away entirely for me. To be seen whether there are other issues though.

@fdncred
Copy link
Collaborator

fdncred commented Dec 11, 2023

This looks like some pretty deep surgery. I'm interested in trying it out after the CI is green. Thanks for taking a look at this. I'm always interested in these type of performance improvements as long as the performance is all that is changed.

@rtpg
Copy link
Contributor Author

rtpg commented Dec 11, 2023

So there are definitely changes across many files but in practice the main change here is in the Stack created for the REPL provided by nu-cli. We wrap that one in a shared reference, so that it can be shared by NuCompleter , NuHighlighter, and the other plugins that are shared here. There are some downstream changes from having those objects hold onto an Arc (i.e. ShareableStack, which is just a type alias for Arc<Mutex<Stack>>), which makes this look very cross cutting but it's only touching some stuff.

The biggest sort of trickiness of course is that when using a mutex you have to be carefaul about locking. I tried to use explicit scopes when possible but for the big "main loop" of the REPL there were some places where drop simply was more ergonomic in the long run IMO. But it's tricky (locks are messy, who know!)

 Stack clones end up copying all the values inside of enviornment
 variables, which can be super costly if we have very large variables
 inside of the environment. Instead, we can pay some small costs to lock
 the environment (at least that's the theory).

 How to experience a huge slowdown on your machine:
 `let $stuff = lsof | from ssv -m 1`
 (This is routinely a 6-digit-length table)
@rtpg
Copy link
Contributor Author

rtpg commented Dec 11, 2023

I did one force push here to get tests back up to snuff, and CI green. There are a lot of mutex unwraps of the "if this unwrap fails there's a deadlock" variety. The test code got a bit less ergonomic here, I think really the best thing might be to rework a helper for the completion tests, but I'm trying to avoid getting too much in the weeds at first.

@fdncred
Copy link
Collaborator

fdncred commented Dec 11, 2023

I'm not sure this will fly with the rest of the core-team with all the expects in place of unwraps, but at least it should be good enough to test it. We'll have to wait and see what others say.

@IanManske
Copy link
Member

IanManske commented Dec 11, 2023

Thanks for taking a shot at this, it has been a long-standing issue: #9048, #6979, #5236, etc.
Another possible solution I brought up once is to use copy-on-write data structures (e.g., ecow) in Value to have constant time clones.

@fdncred
Copy link
Collaborator

fdncred commented Dec 12, 2023

I've trying to test this on my mac and I'm not noticing a benefit with let $stuff = lsof | from ssv -m 1.

5912ms with pr
5298ms without this pr

12,519 items in this list

@rtpg
Copy link
Contributor Author

rtpg commented Dec 12, 2023

@IanManske so I was thinking COW would cause a problem if, say, your prompt command had stack-affecting side effects. But I didn't realize that captures of mutable variables weren't allowed!

Is it possible that maybe a prompt command couldn't have side effects (for the purposes of a stack anyways)?
What might be an interesting strategy here would be to have &Stack variants of eval_subexpression and friends (instead of &mut Stack), that only support read-only operations. This is tricky because it's not like a prompt function can't have any variables, but "it can't make any visible changes to its parent scope" feels like something that could lead us to having &Stack-only calls.

If those existed, then the prompt generator would not need a mutable reference. I don't believe that the completion engine needs one either (except for the fact that the prompt generator needs one, so we need to Arc-ify this stuff anyways).

EDIT: just realized you were talking more about Value itself. I do think here I was able to land on the best case of "replacing lots of cloning work with a lock", but in general having Value be COW feelse like a no-brainer if it's transparent

@rtpg
Copy link
Contributor Author

rtpg commented Dec 12, 2023

@fdncred it's not the command itself that is slow. It's that after running the command, every time you get your prompt it's slow. So the process is let $stuff = some huge value, then hit <Enter> a lot, and notice how much time it takes for things to run.

2023-12-11 06:24:40.424 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:703:13 processing line editor input took 2.205753763s
2023-12-11 06:24:40.424 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:712:13 finished repl loop took 2.22100536s
2023-12-11 06:24:40.424 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:190:13 get guaranteed cwd took 2.154µs
2023-12-11 06:24:40.424 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:205:13 merge env took 12.574µs
2023-12-11 06:24:40.424 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:219:13 reset ctrlc took 171ns
2023-12-11 06:24:40.424 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:233:13 reset sig_quit took 70ns
2023-12-11 06:24:40.429 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:253:13 get config/cursor config took 4.84614ms
2023-12-11 06:24:40.429 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:288:13 reedline builder took 14.818µs
2023-12-11 06:24:40.429 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:309:13 reedline coloring/style_computer took 882ns
2023-12-11 06:24:40.432 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:324:13 reedline menus took 3.006213ms
2023-12-11 06:24:40.432 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:348:13 reedline buffer_editor took 99.83µs
2023-12-11 06:24:40.432 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:363:13 sync_history took 131.801µs
2023-12-11 06:24:40.432 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:394:13 keybindings took 26.33µs
2023-12-11 06:24:40.439 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:411:13 pre-prompt hook took 6.658074ms
2023-12-11 06:24:40.439 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:429:13 env-change hook took 271ns
2023-12-11 06:24:40.439 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:441:13 update_prompt took 618.896µs
/mnt/tool/proj/nushell> let $files = lsof  | from ssv -m 1                                                                                                         12/11/2023 03:24:44 PM
lsof: WARNING: can't stat() tracefs file system /sys/kernel/debug/tracing
      Output information may be incomplete.
2023-12-11 06:25:00.095 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/util.rs:309:9 eval_source entry #6 took 15.847034854s
2023-12-11 06:25:00.095 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:703:13 processing line editor input took 19.6556794s
2023-12-11 06:25:00.095 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:712:13 finished repl loop took 19.671252127s
2023-12-11 06:25:00.095 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:190:13 get guaranteed cwd took 2.895µs
2023-12-11 06:25:00.095 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:205:13 merge env took 11.021µs
2023-12-11 06:25:00.095 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:219:13 reset ctrlc took 70ns
2023-12-11 06:25:00.095 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:233:13 reset sig_quit took 80ns
2023-12-11 06:25:00.101 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:253:13 get config/cursor config took 5.804209ms
2023-12-11 06:25:01.870 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:288:13 reedline builder took 1.76878837s
2023-12-11 06:25:01.870 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:309:13 reedline coloring/style_computer took 2.305µs
2023-12-11 06:25:01.873 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:324:13 reedline menus took 2.953321ms
2023-12-11 06:25:01.873 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:348:13 reedline buffer_editor took 134.917µs
2023-12-11 06:25:01.873 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:363:13 sync_history took 124.015µs
2023-12-11 06:25:01.873 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:394:13 keybindings took 30.127µs
2023-12-11 06:25:01.983 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:411:13 pre-prompt hook took 110.22032ms
2023-12-11 06:25:01.983 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:429:13 env-change hook took 351ns
2023-12-11 06:25:02.987 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:441:13 update_prompt took 1.004271513s
/mnt/tool/proj/nushell>                                                                                                          12/11/2023 03:25:10 PM
2023-12-11 06:25:10.862 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:703:13 processing line editor input took 7.874439506s
2023-12-11 06:25:10.862 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:712:13 finished repl loop took 10.767012393s
2023-12-11 06:25:10.862 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:190:13 get guaranteed cwd took 2.685µs
2023-12-11 06:25:10.862 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:205:13 merge env took 18.405µs
2023-12-11 06:25:10.862 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:219:13 reset ctrlc took 241ns
2023-12-11 06:25:10.862 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:233:13 reset sig_quit took 80ns
2023-12-11 06:25:10.866 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:253:13 get config/cursor config took 3.529094ms
2023-12-11 06:25:12.780 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:288:13 reedline builder took 1.914519365s
2023-12-11 06:25:12.780 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:309:13 reedline coloring/style_computer took 1.974µs
2023-12-11 06:25:12.783 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:324:13 reedline menus took 2.912533ms
2023-12-11 06:25:12.783 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:348:13 reedline buffer_editor took 113.626µs
2023-12-11 06:25:12.783 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:363:13 sync_history took 122.553µs
2023-12-11 06:25:12.783 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:394:13 keybindings took 25.789µs
2023-12-11 06:25:12.908 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:411:13 pre-prompt hook took 124.827178ms
2023-12-11 06:25:12.908 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:429:13 env-change hook took 360ns
2023-12-11 06:25:13.821 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:441:13 update_prompt took 912.39407ms
/mnt/tool/proj/nushell>                                                                                                          12/11/2023 03:25:16 PM
2023-12-11 06:25:16.976 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:703:13 processing line editor input took 3.154927601s
2023-12-11 06:25:16.976 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:712:13 finished repl loop took 6.113621335s
2023-12-11 06:25:16.976 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:190:13 get guaranteed cwd took 2.835µs
2023-12-11 06:25:16.976 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:205:13 merge env took 14.408µs
2023-12-11 06:25:16.976 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:219:13 reset ctrlc took 581ns
2023-12-11 06:25:16.976 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:233:13 reset sig_quit took 70ns
2023-12-11 06:25:16.980 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:253:13 get config/cursor config took 4.201912ms
2023-12-11 06:25:18.915 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:288:13 reedline builder took 1.935458915s
2023-12-11 06:25:18.915 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:309:13 reedline coloring/style_computer took 2.204µs
2023-12-11 06:25:18.918 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:324:13 reedline menus took 3.051957ms
2023-12-11 06:25:18.919 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:348:13 reedline buffer_editor took 111.612µs
2023-12-11 06:25:18.919 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:363:13 sync_history took 130.809µs
2023-12-11 06:25:18.919 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:394:13 keybindings took 31.459µs
2023-12-11 06:25:19.039 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:411:13 pre-prompt hook took 120.504158ms
2023-12-11 06:25:19.039 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:429:13 env-change hook took 361ns
2023-12-11 06:25:19.975 AM [INFO ] nu_utils::utils: perf: crates/nu-cli/src/repl.rs:441:13 update_prompt took 935.946031ms

notice how update_prompt went from taking 600 microseconds to ~1 second. Later measurements/overriding Value::clone made me find that the source of this was the creation of the stack copies that would cause the prompt to be much slower.

(processing line editor timings are a bit weird of course because that's also just the time it takes to go from "starting the loop" to "getting the enter from reedline and processing it)

@fdncred
Copy link
Collaborator

fdncred commented Dec 12, 2023

notice how update_prompt went from taking 600 microseconds to ~1 second.

gotcha!

@sholderbach
Copy link
Member

I think @kubouch is our resident expert for the stack and scope intricacies.

My guess is that we can not mutably share the whole stack between the primary execution and all the reading utilities like completions/prompt etc. if a mutation to the stored values would occur that would affect the main state of the program.

If a ShareableStack struct could rule that out or behave like a CopyOnWrite structure that only modifies if necessary and share an immutable reference to the ground truth state, we could guarantee that. Not so sure about an Arc<Mutex<Stack>> here. (Intrusive Copy-on-write as @IanManske mentioned probably requires a much deeper intervention)

Our mutable capture prevention logic I think is implemented in the scoping logic elsewhere.

@kubouch
Copy link
Contributor

kubouch commented Dec 15, 2023

Eliminating Stack clones, or any big clones, is highly desirable, but I'm not entirely sure about the impact. We might need to elevate to Sophia. A few thoughts, anyway.

I noticed a potential semantic problem with the PR. Completer, prompt update, and running any closure in general shouldn't have any side effects. The purpose of stack.clone() there is to isolate the changes from the parent scope. Say you define let x = 10 in your custom prompt closure, it will be stored in the cloned stack which will get dropped after the closure ends. If you get the Stack from Arc, you'd be saving x to the parent scope. You don't want to modify the parent stack, unless you're running things like do --env or commands defined with def --env, but these again are handled with clone and env updates are done after the run.

Maybe a potential solution could be to have a data structure like

struct StackSnapshot {
    parent: &Stack,
    stack: Stack
}

with an API identical to Stack where each call would be resolved first on stack, then on the parent. For example, StackSnapshot::get_env_var() would first look for the env var in its stack, then look in parent. So, the completer etc. would still accept stack: &Stackbut instead ofstack.clone(), they'd do let stack = StackSnapshot::new(stack). Something like that, could be probably refined better, you might need a Rcinstead of plain reference, etc. Perhaps this could be even adopted to remove _all_stack.clone()`s in our runtime?

Regarding locking, Nushell is essentially a single-threaded application, so we shouldn't need to worry about locking and use just references/Rc? One notable exception here is par-each which runs multi-threadedly, but that's encapsulated within Nushell's execution model which should be exclusively single-threaded. Not 100% sure about this since we have Arcs in the code already for some reason.

@rtpg
Copy link
Contributor Author

rtpg commented Dec 16, 2023

Completer, prompt update, and running any closure in general shouldn't have any side effects.

I think if this is the rule, then there really should be some abstraction that codifies running without side effects (potentially through a cheap COW clone?). Right now the prompt updater can call some prompt command. That prompt command having side effects that just magically don't appear later seems way more confusing than, say, side effects actually blowing things up. If they really aren't supposed to have side effects, is there a way to enforce that?

I mentioned this before though, but is there actually a way for a function to even have side effects on the global state? Closures aren't allowed to capture mutable variables according to my tests, so is it possible that "a function call in itself will not affect global state" is an actual property of the system here? Can I write a prompt updater that would theoretically bump a value on each call? I couldn't find such a thing.

@rtpg
Copy link
Contributor Author

rtpg commented Dec 16, 2023

FWIW I think the layering sounds good in theory, but I'm worried that that would require rewriting a bunch of Stack methods to instead take some trait (for example eval_expression would need to be able to handle StackSnapshot and Stack). But with layering it would be at least cheap to do the clones

@kubouch
Copy link
Contributor

kubouch commented Dec 17, 2023

Functions (=commands) and closures cannot have a direct impact on the global state. The environment preserving appears on a high level that it changes the parent environment (i.e., a global state), but in fact, only a local environment is modified inside the function which is applied on the parent environment after the function is done running.

I think the only way to alter a global state is via writing to files or, recently, the stor family of commands modifying an in-memory database.

The StackSnapshot was just an immediate idea. Before doing it, a thorough design should be made, and I think it should be adapted into the entire runtime model to avoid having two different models.

@rtpg
Copy link
Contributor Author

rtpg commented Dec 18, 2023

I would like to be able to move forward here in one way or another, given the real nature of this performance issue.

There seems to be three ways forward here (which do not conflict at all, in my opinion, but some might see as a side-step/step back in code quality):

  • avoid the existing cloning by using a shared mutable reference to the same clone (existing PR). Introduces locking trickiness, and makes the plugin code messier due to the locking. Also releases the pressure valve on further improvements here.

  • make it cheaper to clone Value, likely with copy-on-write semantics. I don't know how much code that would affect overall (in particular we'd want to make sure that mutable borrows really only happen when it's needed), but it might work smoothly. Someone above mentioned ecow, I'm agnostic to all of this.

  • Make it cheaper to clone Stack, likely through some "stack layer" principle. I am not sure what the best design is for something like this is (just sticking an optional parent reference into Stack that would really only be used for supporting clone()?). I think there's some risks here of "I modified some state and it doesn't appear later" issues, but my mental model here is that not allowing mutable captures at least prevents this being an issue right now.

I don't mind putting some effort into exploring alternatives, but I don't really want to throw myself into any of these if other people have a stronger idea of what is right. At the very least I hope that this PR can at least highlight that there are three ways of tackling the specific performance issue I was facing.

@kubouch
Copy link
Contributor

kubouch commented Dec 26, 2023

As explained above, I believe that sharing a mutable reference of Stack would have different semantics than clone and would be incorrect.

These are all important points, but they probably require rather deep changes. I'll try to bring it up in one of our core team meetings which we hold on Discord every Wednesday, we likely need Sophia to give her input. The meetings are open to anyone, or you can just drop by our design discussion channel.

@IanManske
Copy link
Member

IanManske commented Dec 27, 2023

Hi @rtpg, in this week's meeting we decided that the "stack layer" / "split stack" approach that you and @kubouch mentioned looks the most promising. Of course, there still might be some things to work out and explore. One thing that I will point out for everybody is that reedline needs Box<dyn Completer> which I believe means that any Completer must have a 'static lifetime. So, the StackSnapshot type that @kubouch proposed in passing might not work as it currently is, since it needs a lifetime for the parent Stack.

[Edit] One crazy idea I just had:
What might work is to create an Arc<Stack> at the start of / before evaluate_repl. This will be given to NuCompleter, and it can be used as the parent to create StackSnapshots. Then, after read_line finishes we know completions will not be run anymore, so we can drop/overwrite the completer (which should set the reference count back to 1). Then, we should be able to call Arc::get_mut and apply updates to the mutable stack. On the next repl loop iteration, the Arc<Stack> will have updated and it will be used to recreate another NuCompleter.

@rtpg
Copy link
Contributor Author

rtpg commented Jan 26, 2024

Working on a new changeset, hope to have another PR up by the end of the day.... but a couple things I noticed:

  • a StackLike impl that would let us us Stack or StackSnapshot does not seem to work straightforwardly. Why? Becuase the Command trait takes a Stack in its run method. If we replace that with an impl StackLike, Command is no longer object safe, so for example we can't store Commands in Alias anymore. Object safety issues.

  • DataSaveJob spins up a thread, so we do indeed need Arc instead of Rc (in theory at least, maybe something can be reworked there).

I believe I have a nice idea to get us across the finish line here, though. Will post details with some code in a bit.

@rtpg
Copy link
Contributor Author

rtpg commented Jan 26, 2024

Alright the PR isn't happening just yet (getting late where I'm at), the short version of what I did though:

  • add a parent_stack optional attribute (as an Arc<Mutex<Stack>>) to Stack, and change lookup to look at it if present (there's also some other details to get deletions working)
  • Manage the main stack in repl.rs as an Arc<Mutex<Stack>>

The end result is that the changes are limited to stack.rs and repl.rs. The repl code gets a bit more futzy, but the damage is relatively limited.

Other attempts that fell apart:

  • StackLike impl instead hit object safety issues, because we could not have a trait method take an impl StackLike and be object safe

  • Replacing Stack.vars with some shareable immutable datastructure ended up with lots of stuff requiring mutable stacks. Cloning in particular was still always costly

  • Using Rc instead of Arc lead to issues with DataSaveJob

@kubouch
Copy link
Contributor

kubouch commented Jan 27, 2024

I think having a parent stack might work (we'd create a linked list of stacks, basically). I'm just not sure if it needs to be Mutex since we don't need to (and shouldn't) mutate the parent stack. The concern here is performance: If we fetch something from the parent stack in a hot loop, we'd be spamming the lock. But I think having just Arc<Stack> might work?

stormasm pushed a commit that referenced this pull request Jan 31, 2024
While working on #11288 I was having some trouble tracking the main REPL
loop, so I've sent in a bunch of tiny refactorings on this branch.

These are almost all of the "move code from one place to another"
variety, and each commit is meant to be independent, _except for the
last one_, which is trying to be a bit more clever to handle the
decision of autocd'ing vs running a command. Feel free to just go
through each commit and cherry pick the ones that look good.

This leads to `evaluate_repl` going from ending on line 715 to ending on
line 395. Again, this is mostly just moving code around, but I think
this set of changes will make other changes around juggling the stack to
avoid cloning easier to review.
@rtpg
Copy link
Contributor Author

rtpg commented Feb 10, 2024

I have opened a new PR, #11654, to tackle this. It implements a notion of stack parents through Arc<Stack>, and includes a property test to help validate the correctness. It doesn't solve issues with large environment variables, but does solve it for large variables.

@rtpg rtpg closed this Feb 10, 2024
dmatos2012 pushed a commit to dmatos2012/nushell that referenced this pull request Feb 20, 2024
While working on nushell#11288 I was having some trouble tracking the main REPL
loop, so I've sent in a bunch of tiny refactorings on this branch.

These are almost all of the "move code from one place to another"
variety, and each commit is meant to be independent, _except for the
last one_, which is trying to be a bit more clever to handle the
decision of autocd'ing vs running a command. Feel free to just go
through each commit and cherry pick the ones that look good.

This leads to `evaluate_repl` going from ending on line 715 to ending on
line 395. Again, this is mostly just moving code around, but I think
this set of changes will make other changes around juggling the stack to
avoid cloning easier to review.
sholderbach pushed a commit that referenced this pull request Mar 9, 2024
This is another attempt on #11288 

This allows for a `Stack` to have a parent stack (behind an `Arc`). This
is being added to avoid constant stack copying in REPL code.

Concretely the following changes are included here:
- `Stack` can now have a `parent_stack`, pointing to another stack
- variable lookups can fallback to this parent stack (env vars and
everything else is still copied)
- REPL code has been reworked so that we use parenting rather than
cloning. A REPL-code-specific trait helps to ensure that we do not
accidentally trigger a full clone of the main stack
- A property test has been added to make sure that parenting "looks the
same" as cloning for consumers of `Stack` objects

---------

Co-authored-by: Raphael Gaschignard <rtpg@rokkenjima.local>
Co-authored-by: Ian Manske <ian.manske@pm.me>
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.

None yet

5 participants