WPF Hanging in Infinite Rendering Loop
I ran into a nasty WPF issue recently with Markdown Monster. Markdown Monster has been for the longest time targeted at .NET 4.6.2, but a while back I started integrating with some libraries that have moved over completely to use .NET Standard. I was hoping by moving to .NET 4.7.1 and more built-in library support for .NET Standard would alleviate the DLL proliferation/duplication that using .NET Standard in full framework seems to entail. Alas that turned out to be a dead end as it didn't help in that respect.
But since I moved I figured what the heck, I'll stay on 4.7.1 - over 97% of users of MM are on 4.7.1 or later and there are a number of improvements in the framework that make this probably worthwhile. I shipped the last minor version update 1.15 with a 4.7.1 baseline target.
Lockups with the 4.7.x Target
A few days later a few oddball bug reports came in on Github Issues. Several existing users were complaining that the new version they just upgraded to was locking up on startup. Sometimes before any UI comes up, sometimes the form loads but is unresponsive (white screen of death).
I tried to duplicate, and was unable to. I tried running MM on 4 different machines I have here locally, and a three more virtual machines that I have access to. Nothing failed. Must be a hardware issue, right? We ran into some WPF rendering issues a long while ago due to specific graphics cards, so I figured this must be similar. The fix there was to disable hardware accelleration, but that seemed to have no effect on the affected users.
It took a lot of back and forth messages to pin this one down and I finally was able to duplicate the error after I switched my display to scale 150%. 4 machines, none of them failed because they all ran 100% until I switched to 150% when I managed to make two of them fail. It turns out the scaling wasn't the actual issue, but it's something that brought out the problem.
Notice how the main window partially loads, but isn't quite complete and stuck half way in the process.
This is an insidious bug because of course I didn't think about a system bug, but rather "Shit, what did I break?" Off I go on a rabbit hunt, only to come up blank rolling back code until prior to the last version update which preceeds the move to 4.7.1. Even then I did not make the connection right away. It wasn't until I started searching for similar reports which was tricky given the vague behavior.
Several people reported rolling back to 1.14.x releases and not seeing any problems and that time I started to realize that the 4.7.1 target was likely causing the problem. I rolled back and sure enough, running the same exact code in 4.6.2 that was crashing in 4.7.1 caused no problems on any of the machines. I pushed out a quick pre-release and several of the affected people tried it out and they too didn't have problems.
We found the problem - yay!
Debugging this Issue
As you can imagine finding a finicky bug like this is a pain in the ass. It took me forever just to come up with a reproducible scenario. Once I figured out how to reproduce I still was unable to really get any useful information. I'd run the app in the debugger and it would hang. Pause the app and then take a look at the call stack.
The call stack wasn't much help either though. It stuck on App.Run() and then off deep into WPF. As you can see there's no user code in the stack, it's basically an infinite loop that keeps re-iterating deep inside of the WPF internals.
My search foo also wasn't helping me much. This is a hard issue to search on as WPF freezes are apparently common, but most were relative to perfectly normal situations like an ordinary race condition (LOL).
Finally I landed an obscure Github Issue that references a StarDefinitionsCanExceedAvailableSpace setting that was introduced in WPF in 4.7.x. This setting disables a new behavior introduced in 4.7.x that more efficiently crashes - uh, I mean manages WPF Grid sizing. This funky app.config runtime configuration setting can roll back the new behavior to the old behavior found in 4.6.x. And that's another fix that worked and still allows staying with 4.7.1 without being hit by this bug.
In my case I ended up rolling back to 4.6.1 anyway though. As mentioned the 4.7.1 move didn't get me what I wanted (less DLL dependencies from .NET Standard references) and for deployment on Chocolatey running anything past 4.6.2 complicates the automated installer testing they do to verify packages as it requires a .NET Runtime update to their clean virtual machine.
For now problem solved - after a harrowing week that probably turned away tons of people from Markdown Monster cause it crashed 😒
If you're interested you can follow the whole Markdown Monster paper trail on this issue in this GitHub issue:
HiDPI Display w/Scaling Can Cause App Freeze on Launch
Thanks to those that stuck through all the back and forth and tried to help me figure out what was going on. You guys are awesome!
Here's some more info on the resolution.
WPF Grid Sizing Bug in .NET 4.7.x
Long story short, I managed to duplicate the bug by having a high scale mode and all users that reported the issue also were using high scale modes.
But, it turns out the scale mode wasn't the actual cause of the failure, but rather a symptom which was exacerbated by scaling the display.
The problem is a bug in .NET 4.7.1 that is specific to applications that are targeted at .NET 4.7.x and hit a specific sizing issues. It's a Grid sizing bug caused by infinite loop entered due to a very minute rounding error. This bug occurs only if your application targets .NET 4.7.x - if you target 4.6.x or now 4.8.x (it's fixed there) the problem does not occur. So you can target 4.6.x and run on 4.7.x and there is no problem. But if you target 4.7.x and run on 4.7.x that's when there's a potential problem.
The final outcome and summary of the problem is best summarized by Sam Bent from Microsoft in a separate Github issue:
The gist of that summary is that when an app is compiled for 4.7.x there's a possibility that grid sizing can cause the application to seize up in an infinite loop that can lock up an application hard.
Workarounds and Solutions
There are a few ways to work around this problem:
Rollback your build target to a pre-4.7.x release
Use the StarDefinitionsCanExceedAvailableSpace
Wait for .NET 4.8
Rolling back was the solution I used, because I didn't find out about the availability of the switch until after I rolled back, and because frankly I didn't see much benefit by staying on 4.7.1. It was important to get this resolved quickly that's where it sits today for me with Markdown Monster.
Using the StarDefinitionsCanExceedAvailableSpace Override
This setting overrides the new GridRendering behavior and basically lets you run with a .NET 4.7.x target in your project, but keeps the old behavior that was used in previous versions.
There is a configuration setting that can be set in app.config for your application:
<AppContextSwitchOverrides value="Switch.System.Windows.Controls.Grid.StarDefinitionsCanExceedAvailableSpace=true" />
I can verify that using that switch lets me run 4.7.1 and not see the lock up in any scaling mode. After I had my running version in 4.6.2 back, I once again moved up to 4.7.1 in a separate branch to try this out and sure enough the switch made the application run targeted with 4.7.1. So there's a hacky workaround.
It's a workaround though. This 'fix' according to Microsoft improves grid rendering allocations, providing more precise sizing and also improves performance and reduces memory usage. On paper this is a great improvement, but... well, side effects 😃
I suspect this issue is not wildly common as there was not very much info to be found about it. I think Markdown Monster makes this issue come up because the startup sequence has a lot of window manipulation. MM uses the MahApps UI framework which uses Window Animation and extra rendering frames for the main window, and MM itself moves the window offscreen for initial render and adjusts window sizing based on screen sizes and DPI settings if the window doesn't fit on the screen or would otherwise be offscreen. IOW, there's a bit of gyration to get the initial window onto the screen that is more likely to hit this bug than a simple WPF form.
So I doubt that every application needs to worry about this, but if you have a 4.7.x WPF app it might be a good idea to try it out at various resolutions and scale levels just to see how it fares.
.NET 4.8 Fixes this Bug
I haven't tried this myself as I don't have .NET 4.8 installed yet, but according to Sam and Vatsan from Microsoft it appears this WPF bug has been fixed in .NET 4.8. Yay!
But also not so Yay, because it'll take a while before we can target 4.8 apps and expect a decent user base for general release application.
It sure would be nice if this bug fix could be patched back into 4.7. 4.7 has the vast majority of .NET runtime user base today and it'll probably be quite a while before we can target 4.8 for public release applications when 4.8 reaches critical mass. Recent Windows 10 auto-updates help but that only applies to Windows 10 users (which luckily in MM is most of them, but for other more mainstream apps is probably not the case).
In the meantime this insidious bug can catch a lot of developers off guard with a really hard to track down bug. Hopefully this post might help pointing people in the right direction.
It's always a bummer to see bugs like this creep up. It sure seems like a major bug, but again searching turned up almost no hits which makes me think that not a lot of people are hitting this issue. Maybe most people target lower versions of .NET as I do - it seems I'm always targeting one point version behind the current latest version and that might account for the relatively low hit rate.
Still it's disconcerting to hit a bug like this that's so random and yet sounds like it could potentially hit just about any app. After all who's not using WPF Grids everywhere in a WPF application, it would seem difficult not to hit this at some point if it's random calculation error. I'd be interested to hear about others that have run into this issue and under what circumstances. If you have please leave a comment with your story.
I hope writing this down will make it easier for people to find this info in the future - I sure would have appreciated this instead of a week of lots of harried customer bug reports and no answers for them (and actually being a bit high and mighty with my Works on my Machine attitude).
this post created and published with
© Rick Strahl, West Wind Technologies, 2005-2019Posted in WPF
Ad Blockers, Brave Browser and BrainTree Credit Card Processing SDKs
Good News, Bad News
The good news is works very well. Order processing is very quick through this remote interface and with BrainTree at least as a bonus you can process PayPal requests just as you do Credit Cards using the exact same process with a different 'card type' - no need to use a separate PayPal order flow. Nice.
But - and there's always a but - today I noticed a fairly major problem: For the last few months I've been using the Brave Browser for most of my Web browsing. Brave is a Chromium based browser that provides most of the features of Chrome without Google tracking you each step of your browsing adventures. Brave also provides built-in ad-blocking by default so overall the browsing experience out of box is much better than you get in stock Google Chrome, because a lot of the crap ad content that makes up a good part of the Web these days is not being loaded.
When visiting one of the payment pages in my store with Brave, I noticed that the payment page wasn't working. Basically the remote payment form wasn't showing up.
Here is a side by side figure of Chrome and Brave of the same order form page: (Chrome on the left, Brave on the right):
Notice that Brave doesn't render the payment form and if I open the DevTools I can see that it's failing because of CORS policy.
My first thought is that something was wrong with BrainTree's CORS policy that they are pushing down to my site, because typically CORS errors are related to missing sites in the content policy and CORS headers that are returned from the target SDK server.
Content Blocking in Brave
But alas it turns out that the problem isn't really the CORS headers but rather the fact that Brave is blocking third party cookies.
Now, I can now go in and manually disable that option and then get the page to work properly:
This is likely to be a problem with not just Brave Browser, but any content/ad blocker since third party cookies are a primary source of identity tracking. Unfortunately in this case the third party cookie is required for operation of the order form and not for tracking purposes.
So while there's a workaround to the non-loading page, it's not really something that a user can readily figure out which is a pretty big problem for an order form, especially if the user was ready to pay. The last thing we want to do at that point is make the user go - "wait what" or worse "WTF?"...
So, how to address this problem?
Server Side Processing
I'm not sure if there's a real technical solution unless you want to fall back to server side processing which for security reasons is not a good idea.
In my custom store I can actually process either server side or using the client form. But because of the PCI requirements and liabilities, falling back to the server side processing is simply not an option for me. However, this might be for a larger company that has gone through their own PCI certification.
The only other solution I see is to provide some help to the user should they find themselves in this situation. I've added a link to the form that takes the user to a documentation page that describes what they should see and with some explanation on turning off content blockers.
This is not very satisfying but hopefully it might help keep people who hit this problem on the site and get them to disable their content blockers.
Ah - progress. By offloading payment processing to a remote service I've solved one thorny problem (PCI) and now I've potentially brought in another problem that might keep some customers from being able to place an order. It seems that no matter how you turn things with security, there is always some sort of trade off.
If you're using a browser like Brave you are probably fairly technically savy. It's also very likely that you will eventually run into problems like this with other sites. These days there is so much integration between applications using APIs that require remote scripts and third party cookie integrations and content blockers likely will become a more common problem for users in that more and more legitimate content will end up getting blocked. This whitelisting takes a little work, but it's usually still better than the alternative of getting flooded with ads and trackers.
The hard part is realizing that it's happening. In using Brave I often simply forget that it's blocking stuff and when stuff fails my first reaction is that Brave is not doing the right thing, when really it's the content blocker. For less savvy users this is especially the case since they have no idea why a page doesn't work right and thinking of turning the content blocking off won't come natural. Heck it didn't come as the first thought to me - I googled CORS issues with BrainTree initially, before trying a different browser 😃.
this post created and published with
© Rick Strahl, West Wind Technologies, 2005-2019Posted in Web Credit Card Processing
Finding the ProgramFiles64 Folder in a 32 Bit App
You probably know that on Windows using .NET you can use System.Environment.GetFolderPath() to pick out a host of special Windows folders. You can find Local App Data, Programs, My Documents, Pictures and so on using the Environment.SpecialFolder enum .
This function is needed because these special folders often are localized and using this function ensures that the paths are properly adjusted for various localized versions of Windows. You don't ever want to be building special paths by hand as they are likely to break if you run on a differently localized version of Windows. For example here's a link that shows what Program Files in Windows looks like in different languages:
Bottom line is if you need to access Windows special folders always use the GetFolderPath() function and then build your path from there with Path.Combine().
While the function works well there are a number of common paths missing, and some others are a little quirky.
Using ProgramFiles and ProgramFiles32
One of those quirks is the Program Files folder. There are two Program Files folders in Windows the 64 bit versions of Windows most of us are running today:
Program Files (x86)
Here's what this looks like on disk off the C:\ root:
Program Files is for 64 bit apps, and Program Files (x86) is for 32 bit apps on 64 bit systems. On 32 Bit systems there's only Program Files which holds 32 bit applications and there's no support for 64 bit applications at all.
On 64 bit machines, the Program Files location where applications install changes the behaviors of Windows launchers. For example if you compile a .NET Desktop application with Any CPU and you launch from Program Files (x86) you'll launch as a 32 bit app. Launch from Program Files and you'll launch as a 64 bit application. Windows provides a launching process some hints that suggest whether the app should run 32 or 64 bit modes.
So the System.Environment.SpecialFolder enum has values that seem pretty obvious choices for finding those two folders:
But it's never that simple...
Quick, what does the following return when you run your application as a 32 bit application (on 64 bit Windows):
var pf86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
var folder = System.IO.Path.Combine(pf86, "SmartGit\\bin");
var exe = System.IO.Path.Combine(folder, "smartgit.exe");
Here's a hint: Not what you'd expect.
In fact in a 32 bit application you'll find this to be true:
var pf86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
var pf = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
Assert.AreEqual(pf,pf86); // true!
Now repeat this with a 64 bit application:
var pf86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
var pf = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
Assert.AreEqual(pf,pf86); // false
Got that? It's confusing, but in its own twisted way this makes sense. A 32 bit application assumes it's running on a 32 bit system and should look for program files in the Program Files (x86) folder so it returns that folder for ProgramFiles because that's all it knows - 1 folder where 32 bit applications live.
Using 32 bit mode and the SpecialFolder enum there's no way to actually discover the true 64 bit Program Files folder. Ouch!
The Workaround - Using Environment Var
These days you'd be hard pressed to find a 32 bit version of Windows. Most people run 64 bit versions. So if you run a 32 bit application on a 64 bit version of Windows you can use the following code to get the 'real' Program Files folder:
var pf86 = Environment.GetEnvironmentVariable("ProgramW6432");
pf86 = Environment.GetFolder(Environment.SpecialFolder.ProgramFiles)
This gives you the 64 bit Program Files path in a 32 bit application. If the environment variable doesn't exist because you're running an old or 32 bit version of Windows, the code falls back to SpecialFolder.ProgramFiles so it should still work in those environments as well.
Practicalities - Why is this a Problem
If you're running a 64 bit application there really is no problem. In 64 bit mode ProgramFiles returns the Program Files folder and ProgramFilesX86 returns the Program Files (x86) folder. Problem solved right? Yeah - for 64 bit.
But... if you have a a 32 bit application as I do with Markdown Monster you need to use the environment variable to retrieve the right Program Files path.
You might say - just use 64 bit, but in the case of Markdown Monster I run in 32 bit in order to get better performance and stability out of the Web Browser control that is heavily used in this application. 64 bit IE was always a train wreck and the Web Browser control reflects that.
So the app runs in 32 bit mode, but I'm also shelling out and launching a number of other applications: I open command lines (Powershell or Command) for the user, run Git commands, open a GUI git client, various viewers like Image Viewers, explicitly launch browsers and so forth. The apps that are being launched are a mix of 32 and 64 bit applications.
In the example above I open SmartGit which is my GUI Git Client of choice and it's a 64 bit app, hence I need to build a path for it.
Using the code above lets me do that.
I'm writing this down because I've run into this more than a few times and each and every time I go hunting for the solution because I forgot exactly I did to get around it. Now I can just search for this post - maybe it'll help you remember too 😃
this post created and published with
© Rick Strahl, West Wind Technologies, 2005-2019Posted in .NET
The problem here is that if you create a link without an href attribute the link won't show typical link behavior. So this:
<a>Link without an href attribute</a> | <a href="#0">Link with href</a> |
renders to the following with the default browser styling:
Notice that the 'link-less' link renders without link styling. So when using dynamic navigation via event handlers or jQuery etc. you need to make sure that you explicitly specifiy some sort of link in the href attribute.
If you're using a UI framework like BootStrap it will still style missing href links properly. But the default HTML styles render anchors without href with link styling that doesn't trigger the text-decoration styling.
The Short Answer: href="#0"
I've been using various approaches over the years, but probably the cleanest and least visually offensive solution is to use a hash to a non-existing name reference in a page.
So this is what I've settled on:
<a href="#0">Fly, fly, fly away</a>
I like this because:
It doesn't navigate
It makes it obvious that this it's a do-nothing navigation
Link looks reasonable on the status bar
It doesn't flag security scanners
I actually found out that this works only recently and previously I had been using a slew of other approaches, which is what prompted me to write this up.
For a little more background lets take a look.
Empty HREF Link Navigation
Ok, so what options are there? There quite a few actually, some better than others. I would argue some of these aren't an option, but I'll list them anyway:
<a href="#" onclick="return false;" />
Until recently I've been using #5, but just recently discovered that #6 is actually possible and which to me is preferrable.
Here's a little HTML you can experiment with (CodePen):
<li><a href="https://weblog.west-wind.com">Normal Web link</a></li>
<li><a>Link without an href attribute</a></li>
<li><a href="">Link with empty href attribute</a></li>
<li><a href="#0">Link with href and `#0`</a></li>
Don't use an empty HREF
Empty HREF links might be tempting but they are problematic as they basically mean re-navigate the current page. It's like a Refresh operation which resubmits to the server.
Notice that an empty HREF renders as a link with a target URL pointing back to the current page:
This can be deceiving on small pages as you may not actually notice the page is navigating.
Empty HREF links are useful for a few things, just not for dynamic navigation scenarios. It's a good choice for Refresh this Page style links, or for <form href="" method="POST"> form submissions which posts back to the same URL.
Don't use # by itself
As it turns out the second choice is the most commonly used in examples and demonstrations, but this is usually not a good choice because this syntax:
<a href="#">Link Text</a>
actually is not a do-nothing navigation. It causes navigation to the the top of the page. Unless your page is small enough to fit into a single Viewport screen, or every handled link actually explicitly aborts navigation (more on that below), you don't want to use this option.
Handling onclick and Returning false
The links and text are harmless as they literally do nothing, but it's ugly, and to the average non-Web savvy person probably a bit scary.
And the Winner is: #0
This approach works by using a non existing hash link. Unless you have a named link or an ID named 0 - which is unlikely - the navigation fails, which effectively does nothing. No navigation and no scrolling.
If for some strange reason you have an ID or named link called 0 use a different non-existing value for the hash: #foo123 works too 😃
Most Frameworks handle this automatically
So if you're using these frameworks you usually don't set the href attribute at all and let the framework handle that for you, both in terms of styling and the navigation.
This is pretty basic stuff, but it's easy to forget which choices work and which sort of work or provide ugly results. I know I've gone back and forth on this many times in the past before I recently settled on:
<a href="#0">Dynamic Navigation</a>
which seems the cleanest solution.