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

Implement buffer restore #16598

Merged
merged 6 commits into from
Mar 29, 2024
Merged

Implement buffer restore #16598

merged 6 commits into from
Mar 29, 2024

Conversation

lhecker
Copy link
Member

@lhecker lhecker commented Jan 24, 2024

This changeset allows Windows Terminal to dump its buffer contents as
UTF-16LE VT text onto disk and restore it later. This functionality is
enabled whenever persistedWindowLayout is being used.

Closes #961
Closes #16741

Validation Steps Performed

  • Open multiple windows with multiple tabs and restart the app
    Everything's restored ✅
  • Reopen a tab with output from RenderingTests.exe
    Everything's restored ✅
  • Closing tabs and windows with Ctrl+W deletes their buffer dumps ✅
  • Closing tabs doesn't create buffer dumps ✅

@microsoft-github-policy-service microsoft-github-policy-service bot added Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Area-UserInterface Issues pertaining to the user interface of the Console or Terminal Product-Terminal The new Windows Terminal. labels Jan 24, 2024
@@ -124,8 +124,7 @@ namespace winrt::TerminalApp::implementation
return appLogic->GetSettings();
}

AppLogic::AppLogic() :
_reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); } }
Copy link
Member Author

Choose a reason for hiding this comment

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

I believe not monitoring state.json for changes anymore is important as it prevents disturbing the session state while session persistence is still ongoing. That's because when ApplicationState flushes to disk, this FS monitor will be triggered and reload the ApplicationState again.

I figured it's not a problem anymore to do this since we're effectively a singleton application now.

lhecker added a commit that referenced this pull request Jan 25, 2024
lhecker added a commit that referenced this pull request Jan 25, 2024
lhecker added a commit that referenced this pull request Feb 6, 2024
This contains all the parts of #16598 that aren't specific to session
restore, but are required for the code in #16598:
* Adds new GUID<>String functions that remove the `{}` brackets.
* Adds `SessionId` to the `ITerminalConnection` interface.
* Flush the `ApplicationState` before we terminate the process.
* Not monitoring `state.json` for changes is important as it prevents
  disturbing the session state while session persistence is ongoing.
  That's because when `ApplicationState` flushes to disk, the FS
  monitor will be triggered and reload the `ApplicationState` again.
@microsoft-github-policy-service microsoft-github-policy-service bot added Issue-Bug It either shouldn't be doing this or needs an investigation. Priority-1 A description (P1) labels Feb 21, 2024
@lhecker lhecker force-pushed the dev/lhecker/961-buffer-restore branch from 5dbb09a to f9511d9 Compare February 21, 2024 15:23
@lhecker lhecker marked this pull request as ready for review February 21, 2024 15:23
@zadjii-msft
Copy link
Member

notes:

  • In AppHost::_CloseRequested,
    • if the last peasant is closed, we call AppHost::_quit then bail.
    • Otherwise, we tell the monarch to remove us then PostQuitMessage
  • AppHost::_quit
    • hop off to a BG thread, clear the PersistedWindowLayouts, then RequestQuitAll
      • RequestQuitAll handled in Monarch::_handleQuitAll, which will ONCE in a process:
        • call Quit on each peasant, which raises _QuitRequestedHandlers, which is handled in
          • AppHost::_QuitRequested, which
            • calls down to TerminalWindow::Quit
              • which calls down to _root->PersistState(), which saves that window.
            • Then AppHost calls PostQuitMessage to exit it's hwnd
  • LastTabClosed was just replaced by CloseWindowRequested

Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

Most of this I'm cool with, but I have a couple last questions. Honestly, not as scary as I expected

@@ -1713,6 +1695,85 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}

void ControlCore::PersistToPath(const wchar_t* path) const
{
const wil::unique_handle file{ CreateFileW(path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) };
Copy link
Member

Choose a reason for hiding this comment

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

kinda weird to me that the actual mechanics of reading/writing the file are down at the level of the control.

Benefit is that the file contents don't need to ever traverse a winrt boundary. And theoretically, anyone using a TermControl could then also get fairly trivial session restore from a file.

Downsides: we can't reuse our file loader (that Dustin's moving in #15329 so maybe this isn't important), and should other consumers get this? I suppose why not?

Okay I think I'm on board

src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp Outdated Show resolved Hide resolved
src/cascadia/TerminalApp/Pane.cpp Outdated Show resolved Hide resolved
src/cascadia/TerminalApp/AppCommandlineArgs.h Show resolved Hide resolved
src/cascadia/TerminalControl/ControlCore.cpp Outdated Show resolved Hide resolved
src/cascadia/WindowsTerminal/AppHost.cpp Show resolved Hide resolved
src/cascadia/TerminalApp/TerminalPage.cpp Outdated Show resolved Hide resolved
{
const auto settingsDir = CascadiaSettings::SettingsDirectory();
const auto idStr = Utils::GuidToPlainString(id);
const auto path = fmt::format(FMT_COMPILE(L"{}\\buffer_{}.txt"), settingsDir, idStr);
Copy link
Member

Choose a reason for hiding this comment

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

some part of me wants to abstract out this fmt::format call to a common util lib, so that the app and control don't need to be updated in parallel if we change this, but also meh

Copy link
Member Author

@lhecker lhecker Mar 20, 2024

Choose a reason for hiding this comment

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

Yeah, I also felt a bit disgusted about putting this here, especially when considering the counterpart in WindowEmperor. But I couldn't come up with a good way to put it anywhere else. I think the saving grace of this code is that it's at least encapsulated to just 3 "small" sections of TerminalPage and WindowEmperor.

Copy link
Member

Choose a reason for hiding this comment

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

funny enough, it was my plan to add support for automatic recordings which would use state/sessionIdGuid files!

src/cascadia/WindowsTerminal/AppHost.cpp Outdated Show resolved Hide resolved
Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

I'm commander Shepard, and this is my favorite release of the Terminal

Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

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

Honestly I am exceptionally cool with this. Everything makes sense, it simplifies the state persistence logic severely, and it gives us a generic TextBuffer -> VT function (!)

return _index;
}

BYTE GetIndex() const noexcept;
Copy link
Member

Choose a reason for hiding this comment

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

dumb question - does moving this out of the header harm the inlineability of it? does LTCG largely help with that?

Copy link
Member Author

Choose a reason for hiding this comment

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

inline isn't a hint for inlining anymore, so it shouldn't make any difference. LTCG makes no difference either I think, because if anything it's probably inlined during initial compilation already due to its small size.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I don't mean because of the inline keyword. Method bodies in header files can be inlined automatically, by the compiler, without the linker's help (LTCG). If you move the body into the cpp file, it can't be inlined during initial compilation because the compiler can only see the body from a single translation unit. It would require LTCG to come through and inline it at the call sites, now, because the other translation units couldn't see the body at compile time.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah good point. This would be a good reason to move it back to the header.

@@ -340,6 +337,9 @@
<data name="CmdProfileArgDesc" xml:space="preserve">
<value>Open with the given profile. Accepts either the name or GUID of a profile</value>
</data>
<data name="CmdSessionIdArgDesc" xml:space="preserve">
<value>Sets the WT_SESSION variable</value>
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
<value>Sets the WT_SESSION variable</value>
<value>Sets the WT_SESSION variable; must be a GUID</value>

maybe? or, is there an opportunity for like, yeeting an environment variable injection into this arg?

{
const auto settingsDir = CascadiaSettings::SettingsDirectory();
const auto idStr = Utils::GuidToPlainString(id);
const auto path = fmt::format(FMT_COMPILE(L"{}\\buffer_{}.txt"), settingsDir, idStr);
Copy link
Member

Choose a reason for hiding this comment

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

funny enough, it was my plan to add support for automatic recordings which would use state/sessionIdGuid files!

src/cascadia/TerminalControl/ControlCore.cpp Show resolved Hide resolved
}
}

const auto lock = _terminal->LockForReading();
Copy link
Member

Choose a reason for hiding this comment

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

this is the read lock, should it be the write lock?

@@ -32,7 +32,8 @@ namespace Microsoft.Terminal.Control
String ProfileName;
String ProfileSource;

Boolean EnableUnfocusedAcrylic;
Boolean EnableUnfocusedAcrylic { get; };
Guid SessionId { get; };
Copy link
Member

Choose a reason for hiding this comment

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

I didn't see this one get used; do we use it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, via the TerminalSettings object to get the sessionId into the connection.

@@ -1244,6 +1244,13 @@ winrt::hstring CascadiaSettings::_calculateHash(std::string_view settings, const
return winrt::hstring{ hash };
}

// This returns something akin to %LOCALAPPDATA%\Packages\WindowsTerminalDev_8wekyb3d8bbwe\LocalState
// just like SettingsPath(), but without the trailing \settings.json.
winrt::hstring CascadiaSettings::SettingsDirectory()
Copy link
Member

Choose a reason for hiding this comment

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

Oh dang! @zadjii-msft you could have used this for WT_SETTINGS_DIR

Copy link
Member

Choose a reason for hiding this comment

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

THATS WHY I thought this method already existed!

si.cb = sizeof(si);
wil::unique_process_information pi;

LOG_IF_WIN32_BOOL_FALSE(CreateProcessW(nullptr,
Copy link
Member

Choose a reason for hiding this comment

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

holy sh--, multi-window restore starts WindowsTerminal.exe which then gets a handle to our Monarch and sends it a commandline.

What the heck.

We could just... use Monarch directly here. I know you didn't write this code but I think we missed it when we migrated to single-proc.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup. I felt like it would be too off-topic if I changed this part within the PR. But I agree that it's ripe for a refactoring.

@lhecker
Copy link
Member Author

lhecker commented Mar 28, 2024

FYI the merge commit is non-trivial. If you do want to review my latest changes, you may have to review the merge as well.

Comment on lines +2568 to +2586
static constexpr Mapping mappings[] = {
{ CharacterAttributes::Intense, { 22, 1 } },
{ CharacterAttributes::Italics, { 23, 3 } },
{ CharacterAttributes::Blinking, { 25, 5 } },
{ CharacterAttributes::Invisible, { 28, 8 } },
{ CharacterAttributes::CrossedOut, { 29, 9 } },
{ CharacterAttributes::Faint, { 22, 2 } },
{ CharacterAttributes::TopGridline, { 55, 53 } },
{ CharacterAttributes::ReverseVideo, { 27, 7 } },
{ CharacterAttributes::BottomGridline, { 24, 4 } },
};
for (const auto& mapping : mappings)
{
if (WI_IsAnyFlagSet(attrDelta, mapping.attr))
{
const auto n = til::at(mapping.change, WI_IsAnyFlagSet(attr, mapping.attr));
fmt::format_to(std::back_inserter(buffer), FMT_COMPILE(L"\x1b[{}m"), n);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this will work correctly when mixing intense and faint. For example, a sequence like \e[1mBOLD\e[2mBOTH\e[0;2mFAINT will assumedly serialize as \e[1mBOLD\e[2mBOTH\e[22mFAINT, which isn't correct (the FAINT text won't be faint).

It's also worth mentioning that BottomGridLine isn't the same as underline - we don't have a VT mapping for that. So AFAIK, it shouldn't ever show up in the Windows Terminal buffer.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I didn't even notice that I used 22 twice. Thanks for noticing this!
I've opened #16970 which should fix both issues.

Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

I'm fine dealing with the intense/faint thing in post.

@zadjii-msft zadjii-msft added this pull request to the merge queue Mar 29, 2024
Merged via the queue into main with commit c4c5206 Mar 29, 2024
20 checks passed
@zadjii-msft zadjii-msft deleted the dev/lhecker/961-buffer-restore branch March 29, 2024 12:21
zadjii-msft added a commit that referenced this pull request Apr 16, 2024
This got lost in #16598. `TerminalPage` needs to ask the window where the it
actually is, so it can persist it.

Closes #17010
github-merge-queue bot pushed a commit that referenced this pull request Apr 17, 2024
This got lost in #16598. `TerminalPage` needs to ask the window where
the it actually is, so it can persist it. More details in
#16598 (comment)

Closes #17010
DHowett pushed a commit that referenced this pull request Apr 23, 2024
This contains all the changes of 5dda507, c4c5206, 9f08ee7, and
5f10159 (#16598 and all related PRs), but without the buffer restore
feature. The hope is that these changes fix some rarer issues
we've been hearing about, where persistence doesn't work correctly.

## Validation Steps Performed
This changeset was tested on Windows 11 with 2 windows and 4 tabs
where 1 tab had 2 mixed split panes. All windows and tabs got
restored properly. It didn't crash on Windows 10.
DHowett pushed a commit that referenced this pull request Apr 23, 2024
This got lost in #16598. `TerminalPage` needs to ask the window where
the it actually is, so it can persist it. More details in
#16598 (comment)

Closes #17010

(cherry picked from commit 643f716)
Service-Card-Id: 92360686
Service-Version: 1.20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-UserInterface Issues pertaining to the user interface of the Console or Terminal Issue-Bug It either shouldn't be doing this or needs an investigation. Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Priority-1 A description (P1) Product-Terminal The new Windows Terminal.
Projects
Status: To Consider
4 participants