Closed Bug 301774 Opened 19 years ago Closed 19 years ago

Avoid flushing gfx buffers more frequently than refresh rate (with Cocoa widgets)

Categories

(Core Graveyard :: GFX: Mac, defect)

PowerPC
macOS
defect
Not set
normal

Tracking

(Not tracked)

VERIFIED FIXED
mozilla1.8alpha4

People

(Reporter: krmathis, Assigned: mark)

References

()

Details

Attachments

(1 file, 5 obsolete files)

User-Agent:       Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8b4) Gecko/20050722 Camino/0.9a2+
Build Identifier: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8b4) Gecko/20050722 Camino/0.9a2+

After the bug 280982 checkin my build compiled with 10.4u SDK have became very
slow at redrawing when I type in text boxes on webpages with animated gif images.

Testcase in link above ( from bug 272954 ).

The official build (10.2.8 SDK) and a 10.3.9 SDK build (only sdk difference to
my build) dont have this slowness problem.

Reproducible: Always

Steps to Reproduce:
1. Build Camino with 10.4u SDK.
2. Visit testpage above.
3. Type some text in the text box.

Actual Results:  
Browser slows down. Gif images slowly animates, while it tries to catch up what
I write. Its "beach balling" as well.

Expected Results:  
"Smooth" typing and gif animation (at least on par with the 10.2.8 and 10.3.9
SDK builds)

My .mozconfig: http://homepage.mac.com/krmathis/Trunk/mozconfig.txt
I see this with the 10.4.0 SDK.  Backing out the patch from bug 280982 doesn't
avoid the issue.  Take a look at your own builds from a few days ago, and you'll
probably find that the testcase was always worse with a 10.4 SDK than the
official builds are.
Status: UNCONFIRMED → NEW
Ever confirmed: true
Summary: Slow redrawing when typing in text boxes on pages with animated gif images (10.4u SDK) → SDK >= 10.4: Slow redrawing when typing in text boxes on pages with animated gif images
So they must change something in Cocoa depending on what versions of the libs
you link with. Does Tiger have further NSView drawing optimizations?
(In reply to comment #2)
> So they must change something in Cocoa depending on what versions of the libs
> you link with.

Wouldn't be the first time.  You most likely won't see this behavior if you
build with a 10.4 SDK and run on 10.3.  If you do, it's a problem with the SDK
itself.

> Does Tiger have further NSView drawing optimizations?

Can we take advantage of the stuff they added to allow drawing into a cache? 
http://developer.apple.com/releasenotes/Cocoa/AppKit.html, sorry, no internal
link, scroll to "NSView Drawing Redirection".

The change that caused this bug SHOULD be mentioned on that page, but a quick
search on the word "linked" doesn't turn up anything that jumps out as the
obvious candidate.
Perhaps you are running into issues with the new coalesced updates behavior:
http://developer.apple.com/documentation/Performance/Conceptual/Drawing/Articles/FlushingContent.html
It's Tiger and Tiger-built only.
(In reply to comment #1)
You are correct. Even before the 280982 checkin, my 10.4u SDK build are
noticable slower than the official build. Sorry for not giving accurate
information in my first post.
Camino *does* have exceptionally poor typing performance with coalesced updates
enabled for it, so that's a likely explanation for what you are seeing. You
probably just need to throttle back your flushing.
That sounds plausible. So this is heavily dependent on bug 54153.
Assignee: pinkerton → sfraser_bugs
Depends on: 54153
Attached patch The beginning of a fix (obsolete) — Splinter Review
Simon, I'm not gonna let your having taken this bug get in my way!

I'm typing this in Camino, and for the first time in forever, it doesn't
completely suck.  I'm even linked against the 10.4 libraries.

The attached patch is in no way complete.  It's a proof-of-concept type of
thing.

What I'm doing here is rate-limiting back buffer flushes.  If a flush is
requested too soon, a timer is set with a callback that flushes at a more
appropriate time.  I've done this with icky static variables because the caret
doesn't reuse rendering contexts, and because I can get away with it as long as
the caret's the only thing that calls FlushRect and the port stays the same. 
It is and it does.  That doesn't make this any less hacky.  The math is also
hackish if not altogether wrong, and you can still get caret droppings by
moving around with the cursor keys (this may not be a new problem?)  I know
it's flawed, it's just a starting point.

Where to go from here?	I'm envisioning some sort of global flush manager to
replace and rate-limit QDFlushPortBuffer calls.
Attached patch v0.2 (obsolete) — Splinter Review
In the previous patch, I was flushing at a maximum of 3Hz because that provided
the only thing resembling a rational blink rate on the test page.  That problem
is purely bug 54153 and causes problems with non-Cocoa widgets too, so I won't
work around it here.  I'm now using a sane, but still hardcoded, 30Hz.	This
may change to be display-dependent based on the guidance in Apple's doc.

Drawing issues were caused by resetting the dirty region instead of adding to
it.  The caret droppings should be gone now.

I also fixed the case that would allow a pair of flushes instead of a single
flush if the timer was set but didn't fire until after a FlushRect call.

And warning cleanup too.  That's always healthy.

This patch should be suitable for testing.  From the user's standpoint, it
should be equivalent to whatever final result is produced here.  From the
developer's standpoint, it's not, but hey, it's Saturday night and I just got
home.
Attachment #190269 - Attachment is obsolete: true
(In reply to comment #9)
> Created an attachment (id=190290) [edit]
> v0.2

I just compiled a 10.4u SDK build and I must say this is working a LOT better
than it has for a long time. I even think its smoother than the official build...

Ok, I know its not a final solution. But you definatelly seem to be on the right
track. Great!
I just tested this witha custom build and the attached patch make a HUGE
difference on page with a bunc of animations. This is defenitly the way to go.
This is certainly one approach, and if we don't get to fixing bug 54153, is a
good one. Note also that bug 287813 will make this obsolete.
Depends on: 287813
We are going to land 287813 as early as possible in 1.9. So you should do what
you can for 1.8 but don't bother trying to fix it yourself for 1.9.
Repurposing bug to match what's being done here.  Old summary: "SDK >= 10.4:
Slow redrawing when typing in text boxes on pages with animated gif images"

I'll have a clean patch later or tomorrow.  This will be nice to have for
anything that might like to flush frequently, although it seems that the caret's
the only thing that falls into that category at the moment.
Assignee: sfraser_bugs → mark
Component: General → GFX: Mac
Product: Camino → Core
Summary: SDK >= 10.4: Slow redrawing when typing in text boxes on pages with animated gif images → Avoid flushing gfx buffers more frequently than refresh rate (with Cocoa widgets)
Target Milestone: --- → mozilla1.8alpha4
Version: unspecified → Trunk
Here it is.  This improves the snots out of typing performance with
koko-widgets, nicely implemented in a neat little globally-available flush
manager.
Attachment #190290 - Attachment is obsolete: true
Attachment #190366 - Flags: superreview?(sfraser_bugs)
Attachment #190366 - Flags: review?(pinkerton)
Comment on attachment 190366 [details] [diff] [review]
Rate-limited QD buffer flushes, v1.0

This:
+  if (Hz == 0)
+  {
+    // Initialize the refresh rate.

is a relic and a bad idea.  Initialization won't be conditional on
uninitialized values.
+// Static globally-available flush manager
+NS_EXPORT nsQDFlushManager sQDFlushManager;

rather than exporting the global, you should wrap it in a static function that
returns a ptr to the global. Helps enforce it as a singleton.

+  PRUintn                Hz;                 // Maximum number of updates per
+                                             // second

i'd say add a getter for this to enforce that it's not changable by clients.

+    nsQDFlushPort* port;
+    nsQDFlushPort* next;

don't declare these until you're ready to use them. eg

+    for (port = mPortList ; port ; port = next)

should be |for (nsQDFlushPort* port = mPortList....)|

+  nsQDFlushPort** portPtr;

initialize this to nil for safety...

+  AbsoluteTime now;
+  Nanoseconds elapsed;
+  PRInt64 timeUntilFlush;

same as above, defer declaration until you can initialize.

+  if (!mFlushTimerRunning && timeUntilFlush < 0)
+  {
+    // If past the time for the next acceptable flush, flush now.
+    ::QDFlushPortBuffer(mPort, aRegion);
+    mLastFlushTime = now;
+  }

what happens if the machine goes to sleep while this is happening? will it wake
up and flush a hell of a lot of times, or under-flush? just curious if you've
tested that aspect.

the other question is, does this really need a lock and threadsafety? who else
besides the main thread is going to be doing any drawing? Seems like overhead
that's not needed.
> rather than exporting the global, you should wrap it in a static function
> that returns a ptr to the global. Helps enforce it as a singleton.

How about this?  I've just made the methods and members static.  I'm still
keeping the "s" object around just to get the destructor to fire.  I guess even
that's not necessary since it's at quit time, but I like cleaning up after
myself.  If you think it's better without, I'll get rid of it too.

> i'd say add a getter for this to enforce that it's not changable by clients.

I stuck it in the port object as a static const.

(in CreateOrGet)
> +   nsQDFlushPort** portPtr;
> initialize this to nil for safety...

I'll initialize it to &sPortList instead and take the initialization out of the
for.

> same as above, defer declaration until you can initialize.

Yeah, pre-C99 habit.  I prefer all declarations up top, but I'll go with the
prevailing style.

> what happens if the machine goes to sleep while this is happening? will it
> wake up and flush a hell of a lot of times, or under-flush? just curious if
> you've tested that aspect.

It should flush just right.  ::UpTime stops counting when the machine is
sleeping.  Even if it didn't, with only a single instance per port, we'd see at
most one potentially (but probably not) unnecessary flush.

> the other question is, does this really need a lock and threadsafety? who
> else besides the main thread is going to be doing any drawing? Seems like
> overhead that's not needed.

I tossed that in at the last minute to make it more general.  You're right,
it's not needed here.

Other changes:
Added a TimeUntilFlush method to the port object.  This is used instead of
calculating the time directly in FlushPortBuffer.  The new method also supports
a new check in CreateOrGetPort that will repurpose existing stale port objects
once they're no longer necessary.  Now, in the most common case, the list
should remain only one entry long.  That's nice.

This will now only be part of the build if Cocoa widgets are selected, which
makes it Camino-only and npotdb.  And you know what that means.
Attachment #190366 - Attachment is obsolete: true
Attachment #190757 - Flags: superreview?(sfraser_bugs)
Attachment #190757 - Flags: review?(pinkerton)
Attachment #190366 - Flags: superreview?(sfraser_bugs)
Attachment #190366 - Flags: review?(pinkerton)
Comment on attachment 190757 [details] [diff] [review]
Rate-limited QD buffer flushes, v2

Header first...

>--- /dev/null	2005-07-27 16:11:21.000000000 -0400
>+++ mozilla/gfx/src/mac/nsQDFlushManager.h	2005-07-27 16:21:14.000000000 -0400

>+#include <MacTypes.h>
>+#include <Quickdraw.h>

Shouldn't we just be using Carbon/Carbon.h these days?


>+class NS_EXPORT nsQDFlushManager
>+{
>+public:
>+  nsQDFlushManager            ();
>+  ~nsQDFlushManager           ();

This spacing is unconventional. Choose either:

    nsQDFlushManager();
    ~nsQDFlushManager();
    void			Foopy();

or

				nsQDFlushManager();
				~nsQDFlushManager();
    void			Foopy();

>+
>+  static void                 FlushPortBuffer(CGrafPtr, RgnHandle);

I like to see params named in the header too -- "inPort", "inRegion".

>+  class nsQDFlushPort : public nsITimerCallback
>+  {
>+    friend class nsQDFlushManager;
>+
>+  public:
>+                              nsQDFlushPort(CGrafPtr);
>+                              ~nsQDFlushPort();
>+
>+  protected:
>+    void                      Init(CGrafPtr);
>+    void                      Destroy();
>+    void                      FlushPortBuffer(RgnHandle);
>+    PRInt64                   TimeUntilFlush(AbsoluteTime);
>+    NS_DECL_ISUPPORTS
>+    NS_DECL_NSITIMERCALLBACK
>+
>+    // To use the display's refresh rate as the update Hz, see
>+    // http://developer.apple.com/documentation/Performance/Conceptual/Drawing/Articles/FlushingContent.html .
>+    // Here, use a hard-coded 30 Hz instead.
>+    static const PRUintn      Hz = 30;            // Maximum number of updates

The size of PRUintn depends on the compilation options; it's not a good idea
to use this here. PRUint32 will do fine. We also prefer consts to be prepended
by 'k', so kHz, or, better, kRefreshRateHz.

>+    nsQDFlushPort*            mNext;              // Next object in list
>+    CGrafPtr                  mPort;              // Associated port
>+    AbsoluteTime              mLastFlushTime;     // Last QDFlushPortBuffer call
>+    nsCOMPtr<nsITimer>        mFlushTimer;        // Timer for scheduled flush
>+    PRPackedBool              mFlushTimerRunning; // Is it?
>+  };

I'm not a big fan of nested classes; I'd prefer this be declared and
implemented
in the .cpp file, and just forward-declared here. Someone looking at how to use
this thing shouldn't have to wade through implementation classes.


>--- /dev/null	2005-07-27 16:11:21.000000000 -0400
>+++ mozilla/gfx/src/mac/nsQDFlushManager.cpp	2005-07-27 16:21:43.000000000 -0400

>+// nsQDFlushManager
>+
>+// This sets up a dummy object so the destructor will fire.
>+static nsQDFlushManager sQDFlushManager;

Eh? Is this to prevent dead-stripping or something?

>+nsQDFlushManager::~nsQDFlushManager()
>+{
>+  nsQDFlushPort* next;
>+
>+  for (nsQDFlushPort* port = sPortList ; port ; port = next)
>+  {
>+    next = port->mNext;
>+    port->Destroy();
>+    NS_RELEASE(port);

I find while loops easier to grok:

    nsQDFlushPort* port = sPortList;
    while (port)
    {
	nsQDFlushPort* next = port->mNext;
	port->Destroy();
	NS_RELEASE(port);
	port = next;
    }


>+// CreateOrGetPort(aPort)
>+//
>+// Walks through the list of port objects and returns the one corresponding to
>+// aPort if it exists.  If it doesn't exist, the port object is created, added
>+// to the list, and returned.

Say when the port object will be destroyed.

>+//
>+// protected static
>+nsQDFlushManager::nsQDFlushPort*
>+nsQDFlushManager::CreateOrGetPort(CGrafPtr aPort)
>+{
>+  AbsoluteTime now = ::UpTime();
>+  nsQDFlushPort** portPtr = &sPortList;
>+
>+  // Don't be frightened.  The pointer-pointer business isn't so confusing,
>+  // and it eases maintenance of the linked list.
>+  // First, go through the list and see if an object is already associated
>+  // with this port.
>+  for ( ; *portPtr ; portPtr = &((*portPtr)->mNext))
>+  {
>+    if (aPort == (*portPtr)->mPort)
>+    {
>+      return *portPtr;
>+    }
>+  }

Again, i think this would be clearer with a while loop (and without the weird
double deref):

    nsQDFlushPort* port = sPortList;
    while (port)
    {
	if (aPort == port->mPort)
	    return port;
	port = port->mNext;
    }

>+  // If no port object exists yet, try to find an object that's not in use.
>+  // If there is one, repurpose it.
>+  for (portPtr = &sPortList ; *portPtr ; portPtr = &((*portPtr)->mNext))
>+  {
>+    if (!(*portPtr)->mFlushTimerRunning && (*portPtr)->TimeUntilFlush(now) < 0)
>+    {
>+      // If the flush timer is not running and it's past the time during which
>+      // a flush would be postponed, the object is no longer needed.  Future
>+      // flushes for (*portPtr)->mPort would occur immediately.  Since there's
>+      // no longer any state to track, the object can be reused for another
>+      // port.  This keeps the size of the list manageable.
>+      (*portPtr)->Init(aPort);
>+      return *portPtr;
>+    }
>+  }

Another thing more clearly written with a while loop, and without the extra
deref.

>+  *portPtr = new nsQDFlushPort(aPort);
>+  NS_ADDREF(*portPtr);
>+  return *portPtr;
>+}

>+
>+// RemovePort(aPort)
>+//
>+// Walks through the list of port objects and removes the one corresponding to
>+// aPort, if it exists.
>+//
>+// Presently unused.
>+//
>+// protected static
>+void
>+nsQDFlushManager::RemovePort(CGrafPtr aPort)
>+{
>+  // Traversal is as in CreateOrGetPort.
>+  for (nsQDFlushPort** portPtr = &sPortList ; *portPtr ;
>+       portPtr = &((*portPtr)->mNext))
>+  {
>+    if (aPort == (*portPtr)->mPort)
>+    {
>+      nsQDFlushPort* next = (*portPtr)->mNext;
>+      (*portPtr)->Destroy();
>+      NS_RELEASE(*portPtr);
>+      *portPtr = next;
>+      return;
>+    }
>+  }

Isn't this broken? You have to hook up mNext of the previous port object in the
list to the next one, no?

>+nsQDFlushManager::nsQDFlushPort::nsQDFlushPort(CGrafPtr aPort)
>+: mNext(nsnull)
>+, mPort(aPort)
>+, mLastFlushTime((AbsoluteTime){0, 0})
>+, mFlushTimer(nsnull)
>+, mFlushTimerRunning(PR_FALSE)
>+{
>+}
>+
>+nsQDFlushManager::nsQDFlushPort::~nsQDFlushPort()
>+{
>+  // Everything should have been taken care of by Destroy().
>+}
>+
>+// Init(aPort)
>+//
>+// (Re)initialize object.
>+//
>+// protected
>+void
>+nsQDFlushManager::nsQDFlushPort::Init(CGrafPtr aPort)
>+{
>+  mPort = aPort;
>+  return;
>+}

No need for 'return'.

>+// FlushPortBuffer(aRegion)
>+//
>+// Flushes, dirties, and schedules, as appropriate.  Public access is from
>+// nsQDFlushManager::FlushPortBuffer(CGrafPtr aPort, RgnHandle aRegion).
>+//
>+// protected
>+void
>+nsQDFlushManager::nsQDFlushPort::FlushPortBuffer(RgnHandle aRegion)
>+{
>+  AbsoluteTime now = ::UpTime();
>+  PRInt64 timeUntilFlush = TimeUntilFlush(now);
>+
>+  if (!mFlushTimerRunning && timeUntilFlush < 0)
>+  {
>+    // If past the time for the next acceptable flush, flush now.
>+    ::QDFlushPortBuffer(mPort, aRegion);
>+    mLastFlushTime = now;
>+  }
>+  else
>+  {
>+    // If it's not time for the next flush yet, or if the timer is running
>+    // indicating that an update is pending, just mark the dirty region.
>+    ::QDAddRegionToDirtyRegion(mPort, aRegion);

How does QDAddRegionToDirtyRegion() interplay with QDFlushPortBuffer()?
Have you checked to see that we're just flushging the union of the flush
regions, and not the entire port?

>+
>+    if (!mFlushTimerRunning)
>+    {
>+      // No flush scheduled?  No problem.
>+      if (!mFlushTimer)
>+      {
>+        // No timer object?  No problem.
>+        nsresult err;
>+        mFlushTimer = do_CreateInstance("@mozilla.org/timer;1", &err);
>+        NS_ASSERTION(NS_SUCCEEDED(err), "Could not instantiate flush timer.");
>+      }
>+      if (mFlushTimer)
>+      {
>+        // Start the clock, with the timer firing at the already-calculated
>+        // time until the next flush.  Nanoseconds (1E-9) were used above,
>+        // but nsITimer is big on milliseconds (1E-3), so divide by 1E6.
>+        // Any time that was consumed between the ::UpTime call and now
>+        // will be lost.  That's not so bad in the usual case, it's a tiny
>+        // bit less not so bad if a timer object didn't exist yet and was
>+        // created.  It's better to update slightly less frequently than
>+        // the target than slightly more frequently.
>+        mFlushTimer->InitWithCallback(this, (PRUint32)(timeUntilFlush/1E6),
>+                                      nsITimer::TYPE_ONE_SHOT);
>+        mFlushTimerRunning = PR_TRUE;
>+      }
>+    }
>+  }
>+  return;

Nuke return.


>Index: mozilla/gfx/src/mac/nsRenderingContextMac.cpp
>===================================================================

>-    RgnHandle rgn = ::NewRgn();
>+    RgnHandle rgn = sNativeRegionPool.GetNewRegion();

StRegionFromPool ?
Attachment #190757 - Flags: superreview?(sfraser_bugs) → superreview-
(In reply to comment #19)
> The size of PRUintn depends on the compilation options; it's not a good idea
> to use this here. PRUint32 will do fine. We also prefer consts to be prepended
> by 'k', so kHz, or, better, kRefreshRateHz.

Yeah, I didn't want to use kHz for obvious reasons.  I'll be verbose to work the
k in.

> >+static nsQDFlushManager sQDFlushManager;
> 
> Eh? Is this to prevent dead-stripping or something?

It makes sure the destructor fires while avoiding dead-stripping problems.  Like
I said, the destructor's not really strictly necessary if everything's static
anyway.

> Again, i think this would be clearer with a while loop (and without the weird
> double deref):

(in CreateOrGetPort:)
I can get rid of the pointer-to-pointer in the first loop for sure.  It's
vestigal.  I'm more attached to it in the second pass, though, so that portPtr
points to either the last mNext or, if none, sPortList.  That lets me just stuff
the new object into *portPtr.  I find the alternative to pointer fun ugly and
unnecessary:

  if (sPortList == nsnull)
    sPortList = the_new_object;
  else
    port->mNext = the_new_object;

> Isn't this broken? You have to hook up mNext of the previous port object in 
> the list to the next one, no?

(in RemovePort:)
As above, *portPtr is either sPortList if removing the first or mNext of the
previous if removing anything else.  In this case, the alternative is even uglier.

> How does QDAddRegionToDirtyRegion() interplay with QDFlushPortBuffer()?
> Have you checked to see that we're just flushging the union of the flush
> regions, and not the entire port?

The documented behavior is that QDFlushPortBuffer() flushes only the dirty
region which I'm setting with QDAddRegionToDirtyRegion() plus whatever region is
passed directly to QDFlushPortBuffer().  If that directly-passed region is NULL,
only the dirty region is flushed.

> >-    RgnHandle rgn = ::NewRgn();
> >+    RgnHandle rgn = sNativeRegionPool.GetNewRegion();
> 
> StRegionFromPool ?

(in nsRenderingContextMac.cpp:)
OK, and I'll clean up other occurrences in that file too.
Is there a preferred style to use in preference to static ctors and dtors?

I'm thinking of taking all of the static methods and members in nsQDFlushManager
back to non-static and providing a static nsQDFlushManager *sQDFlushManager in
the class, along with a static sFlushPortBuffer that instantiates
sQDFlushManager if necessary and calls sQDFlushManager->FlushPortBuffer().  The
dummy object to get the dtor firing is stupid, and I'd rather not leak.
Style and readability changes made.  Things that were brought up that I haven't
already commented on are integrated as-is.  I'm still doing pointer-pointer
where it's advantageous to do so, but it should be much more readable now
because I've added explicit shadow derefs and comments that should do a better
job of explaining what portPtr is about.  I've modified the manager class to be
non-static while carrying a static member and associated static flush method as
proposed in comment 21.  The static method is responsible for instantiation,
and arranges for destruction at shutdown via a shutdown observer.  This is all
explained in the comments.
Attachment #190757 - Attachment is obsolete: true
Attachment #190997 - Flags: superreview?(sfraser_bugs)
Attachment #190997 - Flags: review?(pinkerton)
Attachment #190757 - Flags: review?(pinkerton)
Needs #include "nsCRT.h" (but only when building debug?!)
Mark, you might want to take a look at bug 302826
Starting yesterday it caused a failed build on my 10.4.2, GCC 4.0.0, 10.4u SDK
setup.

After I backed out the patch from my source it compiled successfully!
Kai, I can't reproduce what was bug 302826.  None of the ld output you pasted
there is an error, there are only warnings.  If you're still experiencing
trouble, reopen that bug and give me a more complete dump of the final g++ -o
mozilla-bin that fails.
Sorry, I missed the error there because the bug summary was misleading.  Still,
I can't reproduce.  I changed some things between v2 and v3 of the patch that
might cause this error if you don't cleanly and properly back out v2 before
applying v3.  Try pulling a fresh source tree in at least mozilla/gfx/src/mac
and reapplying the most recent version of the patch.
The problem Kai was seeing was due to my having not included the Makefile diff
in 301774.5.  This version corrects that omission, but is otherwise identical.
Attachment #190997 - Attachment is obsolete: true
Attachment #191418 - Flags: superreview?(sfraser_bugs)
Attachment #191418 - Flags: review?(pinkerton)
Attachment #190997 - Flags: superreview?(sfraser_bugs)
Attachment #190997 - Flags: review?(pinkerton)
Attachment #191418 - Flags: superreview?(sfraser_bugs) → superreview+
Comment on attachment 191418 [details] [diff] [review]
Rate-limited QD buffer flushes, v3, includes Makefile patch

r=pink
Attachment #191418 - Flags: review?(pinkerton) → review+
(In reply to comment #27)

Thanks Mark!
After I used the 301774.6.patch Camino built successfully again.
Checked in.
Status: NEW → RESOLVED
Closed: 19 years ago
Resolution: --- → FIXED
Status: RESOLVED → VERIFIED
Depends on: 311618
Product: Core → Core Graveyard
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: