Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Lock the database so we can load all of the tweets into the

database on the background thread.

PReviously I loaded everything on the main thread and that locked
up the thread as we dumped all the junk in there.

Now I use locks on Database.Main to access the databaes and ensure
that the UI thread does not block for long periods
  • Loading branch information...
commit 43c664d379515eb1a876a5e0ce345b949a20fb65 1 parent 4e10b65
Miguel de Icaza authored
10 README
@@ -90,6 +90,16 @@ Design Goals
90 90 the settings to take place right away, even if there is no
91 91 network connectivity
92 92
  93 +Implementation Notes
  94 +====================
  95 +
  96 + In the original TweetStation, I ended up doing a of database
  97 + operations on the main thread, as opposed to doing it on a
  98 + background thread.
  99 +
  100 + I am moving towards locking the Database.Main to allow the
  101 + parsing of initial tweets to take place in the background
  102 +
93 103 OAuth and xAuth
94 104 ===============
95 105
7 TODO
@@ -7,8 +7,11 @@
7 7 Open (for links)
8 8 Quote
9 9
10   - Shrink links should only be enabled if there is a URL in the
11   - text or multiple spaces
  10 + New detailed tweet view, animates insertion of "Reply-To".
  11 +
  12 + Rotated web view ends up without the bottom bar
  13 +
  14 + Probably should not rotate to upside down mode
12 15
13 16 * Missing Stuff
14 17
72 TweetStation/Data/Tweet.cs
@@ -202,30 +202,33 @@ static public int LoadJson (Stream stream, int localAccount, TweetKind kind)
202 202 var start = DateTime.UtcNow;
203 203
204 204 var usersSeen = new HashSet<long> ();
205   - Database.Main.Execute ("BEGIN");
206   - foreach (JsonObject jentry in root){
207   - var juser = jentry [userKey];
208   - bool result;
209   -
210   - if (!ParseUser ((JsonObject) juser, user, usersSeen))
211   - continue;
212   -
213   - if (kind == TweetKind.Direct)
214   - result = tweet.TryPopulateDirect (jentry);
215   - else
216   - result = tweet.TryPopulate (jentry);
217   -
218   - if (result){
219   - PopulateUser (tweet, user);
220   - tweet.Insert (db);
221   - count++;
222   - }
223   -
224   - // Repeat user loading for the retweet info
225   - if (tweet.Retweeter != null)
226   - ParseUser ((JsonObject)(jentry ["retweeted_status"]["user"]), user, usersSeen);
  205 +
  206 + lock (db){
  207 + db.Execute ("BEGIN");
  208 + foreach (JsonObject jentry in root){
  209 + var juser = jentry [userKey];
  210 + bool result;
  211 +
  212 + if (!ParseUser ((JsonObject) juser, user, usersSeen))
  213 + continue;
  214 +
  215 + if (kind == TweetKind.Direct)
  216 + result = tweet.TryPopulateDirect (jentry);
  217 + else
  218 + result = tweet.TryPopulate (jentry);
  219 +
  220 + if (result){
  221 + PopulateUser (tweet, user);
  222 + tweet.Insert (db);
  223 + count++;
  224 + }
  225 +
  226 + // Repeat user loading for the retweet info
  227 + if (tweet.Retweeter != null)
  228 + ParseUser ((JsonObject)(jentry ["retweeted_status"]["user"]), user, usersSeen);
  229 + }
  230 + db.Execute ("COMMIT");
227 231 }
228   - Database.Main.Execute ("COMMIT");
229 232 var end = DateTime.UtcNow;
230 233 Console.WriteLine ("With transactions: Spent {0} ticks in inserting {1} elements", (end-start).Ticks, count);
231 234 return count;
@@ -385,7 +388,7 @@ public static IEnumerable<Tweet> TweetsFromSearchResults (Stream stream)
385 388 }
386 389 }
387 390
388   - public static Tweet ParseTweet (Stream stream)
  391 + static Tweet ParseTweet (Stream stream)
389 392 {
390 393 JsonObject jentry;
391 394
@@ -398,7 +401,8 @@ public static Tweet ParseTweet (Stream stream)
398 401 try {
399 402 var user = new User ();
400 403 user.UpdateFromJson ((JsonObject) jentry ["user"]);
401   - Database.Main.Insert (user, "OR REPLACE");
  404 + lock (Database.Main)
  405 + Database.Main.Insert (user, "OR REPLACE");
402 406
403 407 return FromJsonEntry (jentry, user);
404 408 } catch (Exception e){
@@ -418,7 +422,8 @@ static Tweet FromJsonEntry (JsonObject jentry, User user)
418 422 if (tweet.Retweeter != null){
419 423 user = new User ();
420 424 user.UpdateFromJson ((JsonObject)(jentry ["retweeted_status"]["user"]));
421   - Database.Main.Insert (user, "OR REPLACE");
  425 + lock (Database.Main)
  426 + Database.Main.Insert (user, "OR REPLACE");
422 427 }
423 428 return tweet;
424 429 }
@@ -449,7 +454,8 @@ static void PopulateUser (Tweet tweet, User user)
449 454 //
450 455 public static Tweet FromId (long id)
451 456 {
452   - return Database.Main.Query<Tweet> ("SELECT * FROM Tweet WHERE Id = ?", id).FirstOrDefault ();
  457 + lock (Database.Main)
  458 + return Database.Main.Query<Tweet> ("SELECT * FROM Tweet WHERE Id = ?", id).FirstOrDefault ();
453 459 }
454 460
455 461 public delegate void LoadCallback (Tweet tweet);
@@ -589,7 +595,8 @@ public void UpdateFromJson (JsonObject json)
589 595 // Loads the users from the stream, as a convenience,
590 596 // returns the last user loaded (which during lookups is a single one)
591 597 //
592   - static public IEnumerable<User> LoadUsers (Stream source)
  598 + // Requires datbase lock to be taken.
  599 + static public IEnumerable<User> UnlockedLoadUsers (Stream source)
593 600 {
594 601 JsonValue root;
595 602
@@ -623,7 +630,8 @@ static public User LoadUser (Stream source)
623 630 }
624 631 User user = new User ();
625 632 user.UpdateFromJson ((JsonObject) root);
626   - Database.Main.Insert (user, "OR REPLACE");
  633 + lock (Database.Main)
  634 + Database.Main.Insert (user, "OR REPLACE");
627 635 return user;
628 636 }
629 637
@@ -632,12 +640,14 @@ static public User LoadUser (Stream source)
632 640 //
633 641 public static User FromId (long id)
634 642 {
635   - return Database.Main.Query<User> ("SELECT * FROM User WHERE Id = ?", id).FirstOrDefault ();
  643 + lock (Database.Main)
  644 + return Database.Main.Query<User> ("SELECT * FROM User WHERE Id = ?", id).FirstOrDefault ();
636 645 }
637 646
638 647 public static User FromName (string screenname)
639 648 {
640   - return Database.Main.Query<User> ("SELECT * From User WHERE Screenname = ?", screenname).FirstOrDefault ();
  649 + lock (Database.Main)
  650 + return Database.Main.Query<User> ("SELECT * From User WHERE Screenname = ?", screenname).FirstOrDefault ();
641 651 }
642 652 }
643 653 }
60 TweetStation/Data/TwitterAccount.cs
@@ -80,11 +80,13 @@ public static TwitterAccount FromId (int id)
80 80 return accounts [id];
81 81 }
82 82
83   - var account = Database.Main.Query<TwitterAccount> ("select * from TwitterAccount where LocalAccountId = ?", id).FirstOrDefault ();
84   - if (account != null)
85   - accounts [account.LocalAccountId] = account;
86   -
87   - return account;
  83 + lock (Database.Main){
  84 + var account = Database.Main.Query<TwitterAccount> ("select * from TwitterAccount where LocalAccountId = ?", id).FirstOrDefault ();
  85 + if (account != null)
  86 + accounts [account.LocalAccountId] = account;
  87 +
  88 + return account;
  89 + }
88 90 }
89 91
90 92 public static TwitterAccount Create (OAuthAuthorizer oauth)
@@ -95,7 +97,8 @@ public static TwitterAccount Create (OAuthAuthorizer oauth)
95 97 OAuthToken = oauth.AccessToken,
96 98 OAuthTokenSecret = oauth.AccessTokenSecret
97 99 };
98   - Database.Main.Insert (account);
  100 + lock (Database.Main)
  101 + Database.Main.Insert (account);
99 102 accounts [account.LocalAccountId] = account;
100 103
101 104 return account;
@@ -109,13 +112,15 @@ public static void Remove (TwitterAccount account)
109 112 if (accounts.ContainsKey (id))
110 113 accounts.Remove (id);
111 114
112   - Database.Main.Execute ("DELETE FROM Tweet where LocalAccountId = ?", account.LocalAccountId);
113   - Database.Main.Delete<TwitterAccount> (account);
  115 + lock (Database.Main){
  116 + Database.Main.Execute ("DELETE FROM Tweet where LocalAccountId = ?", account.LocalAccountId);
  117 + Database.Main.Delete<TwitterAccount> (account);
114 118
115   - if (pickNewDefault){
116   - var newDefault = Database.Main.Query<TwitterAccount> ("SELECT LocalAccountId FROM TwitterAccount WHERE OAuthToken != \"\"").FirstOrDefault ();
117   - if (newDefault != null)
118   - Util.Defaults.SetInt (newDefault.LocalAccountId, DEFAULT_ACCOUNT);
  119 + if (pickNewDefault){
  120 + var newDefault = Database.Main.Query<TwitterAccount> ("SELECT LocalAccountId FROM TwitterAccount WHERE OAuthToken != \"\"").FirstOrDefault ();
  121 + if (newDefault != null)
  122 + Util.Defaults.SetInt (newDefault.LocalAccountId, DEFAULT_ACCOUNT);
  123 + }
119 124 }
120 125 }
121 126
@@ -153,19 +158,18 @@ public void ReloadTimeline (TweetKind kind, long? since, long? max_id, Action<in
153 158 (since.HasValue ? "&since_id=" + since.Value : "") +
154 159 (max_id.HasValue ? "&max_id=" + max_id.Value : "");
155 160
156   - Download (req, true, result => {
157   - if (result == null){
158   -
159   - done (-1);
160   - } else {
161   - int count = -1;
  161 + Download (req, false, result => {
  162 + int count = -1;
  163 +
  164 + if (result != null){
162 165 try {
163 166 count = Tweet.LoadJson (new MemoryStream (result), LocalAccountId, kind);
164 167 } catch (Exception e) {
165 168 Console.WriteLine (e);
166 169 }
167   - done (count);
168 170 }
  171 +
  172 + invoker.BeginInvokeOnMainThread (delegate { done (count); });
169 173 });
170 174 }
171 175
@@ -368,15 +372,18 @@ public void Post (string url, string content)
368 372 Url = url,
369 373 PostData = content,
370 374 };
371   - Database.Main.Insert (qtask);
  375 + lock (Database.Main)
  376 + Database.Main.Insert (qtask);
372 377
373 378 FlushTasks ();
374 379 }
375 380
376 381 void FlushTasks ()
377 382 {
378   - var tasks = Database.Main.Query<QueuedTask> ("SELECT * FROM QueuedTask where AccountId = ? ORDER BY TaskId DESC", LocalAccountId).ToArray ();
379   - ThreadPool.QueueUserWorkItem (delegate { PostTask (tasks); });
  383 + lock (Database.Main){
  384 + var tasks = Database.Main.Query<QueuedTask> ("SELECT * FROM QueuedTask where AccountId = ? ORDER BY TaskId DESC", LocalAccountId).ToArray ();
  385 + ThreadPool.QueueUserWorkItem (delegate { PostTask (tasks); });
  386 + }
380 387 }
381 388
382 389 //
@@ -402,13 +409,8 @@ void PostTask (QueuedTask [] tasks)
402 409 // Can happen if we had already favorited this status
403 410 }
404 411
405   - invoker.BeginInvokeOnMainThread (delegate {
406   - try {
407   - Database.Main.Execute ("DELETE FROM QueuedTask WHERE TaskId = ?", task.TaskId);
408   - } catch (Exception e){
409   - Console.WriteLine (e);
410   - }
411   - });
  412 + lock (Database.Main)
  413 + Database.Main.Execute ("DELETE FROM QueuedTask WHERE TaskId = ?", task.TaskId);
412 414 }
413 415 } catch (Exception e) {
414 416 Console.WriteLine (e);
10 TweetStation/Dialogs/EditAccount.cs
@@ -92,10 +92,12 @@ public EditAccount (IAccountContainer container, TwitterAccount account, bool pu
92 92 account.Username = info.Login;
93 93 //account.Password = info.Password;
94 94
95   - if (newAccount)
96   - Database.Main.Insert (account);
97   - else
98   - Database.Main.Update (account);
  95 + lock (Database.Main){
  96 + if (newAccount)
  97 + Database.Main.Insert (account);
  98 + else
  99 + Database.Main.Update (account);
  100 + }
99 101
100 102 account.SetDefaultAccount ();
101 103 DismissModalViewControllerAnimated (true);
22 TweetStation/Dialogs/Settings.cs
@@ -68,17 +68,19 @@ Section MakeAccounts ()
68 68 {
69 69 var section = new Section (Locale.GetText ("Accounts"));
70 70
71   - foreach (var account in Database.Main.Query<TwitterAccount> ("SELECT * from TwitterAccount")){
72   - var copy = account;
73   - var element = new AccountElement (account);
74   - element.Tapped += delegate {
75   - DismissModalViewControllerAnimated (true);
76   -
77   - TwitterAccount.SetDefault (copy);
78   - Util.MainAppDelegate.Account = copy;
  71 + lock (Database.Main){
  72 + foreach (var account in Database.Main.Query<TwitterAccount> ("SELECT * from TwitterAccount")){
  73 + var copy = account;
  74 + var element = new AccountElement (account);
  75 + element.Tapped += delegate {
  76 + DismissModalViewControllerAnimated (true);
  77 +
  78 + TwitterAccount.SetDefault (copy);
  79 + Util.MainAppDelegate.Account = copy;
  80 + };
  81 + section.Add (element);
79 82 };
80   - section.Add (element);
81   - };
  83 + }
82 84 var addAccount = new StringElement (Locale.GetText ("Add account"));
83 85 addAccount.Tapped += delegate {
84 86 Util.MainAppDelegate.AddAccount (this, delegate {
3  TweetStation/UI/Composer.cs
@@ -524,7 +524,8 @@ static void Init ()
524 524 if (inited)
525 525 return;
526 526 inited = true;
527   - Database.Main.CreateTable<Draft> ();
  527 + lock (Database.Main)
  528 + Database.Main.CreateTable<Draft> ();
528 529 }
529 530
530 531 [PrimaryKey]
4 TweetStation/UI/DetailTweetViewController.cs
@@ -203,7 +203,9 @@ public DetailTweetView (RectangleF rect, Tweet tweet, TweetView.TappedEvent hand
203 203 Util.MainAppDelegate.FavoriteChanged (tweet);
204 204 TwitterAccount.CurrentAccount.Post (String.Format ("http://api.twitter.com/1/favorites/{0}/{1}.json", tweet.Favorited ? "create" : "destroy", tweet.Id),"");
205 205 UpdateButtonImage (tweet);
206   - tweet.Replace (Database.Main);
  206 +
  207 + lock (Database.Main)
  208 + tweet.Replace (Database.Main);
207 209 };
208 210
209 211 AddSubview (buttonView);
3  TweetStation/UI/FullProfileView.cs
@@ -66,7 +66,8 @@ void ProcessUserReturn (byte [] res, string diagMsg)
66 66 Root = Util.MakeError (diagMsg);
67 67 return;
68 68 }
69   - user = User.LoadUsers (new MemoryStream (res)).FirstOrDefault ();
  69 + lock (Database.Main)
  70 + user = User.UnlockedLoadUsers (new MemoryStream (res)).FirstOrDefault ();
70 71 if (user == null)
71 72 Root = Util.MakeError (diagMsg);
72 73 else
3  TweetStation/UI/Searches.cs
@@ -152,7 +152,8 @@ public override SearchMirrorElement MakeMirror ()
152 152
153 153 public override void PopulateSearch (Section entries)
154 154 {
155   - entries.Add (from x in Database.Main.Query<User> ("SELECT * from User ORDER BY Screenname")
  155 + lock (Database.Main)
  156 + entries.Add (from x in Database.Main.Query<User> ("SELECT * from User ORDER BY Screenname")
156 157 select (Element) new UserElement (x));
157 158 }
158 159
26 TweetStation/UI/Timeline.cs
@@ -145,7 +145,7 @@ public TimelineViewController (string title, TweetKind kind, bool pushing) : bas
145 145 // that the timeline is continuous
146 146 //
147 147 bool continuous;
148   - IEnumerable<Element> FetchTweets (int limit, long lastId, int skip)
  148 + IEnumerable<Element> UnlockedFetchTweets (int limit, long lastId, int skip)
149 149 {
150 150 continuous = false;
151 151 foreach (var tweet in Database.Main.Query<Tweet> (
@@ -172,7 +172,11 @@ IEnumerable<Element> FetchTweets (int limit, long lastId, int skip)
172 172 public override void ReloadTimeline ()
173 173 {
174 174 long? since = null;
175   - var res = Database.Main.Query<Tweet> ("SELECT Id FROM Tweet WHERE LocalAccountId = ? AND Kind = ? ORDER BY Id DESC LIMIT 1", Account.LocalAccountId, kind).FirstOrDefault ();
  175 + Tweet res;
  176 +
  177 + lock (Database.Main)
  178 + res = Database.Main.Query<Tweet> ("SELECT Id FROM Tweet WHERE LocalAccountId = ? AND Kind = ? ORDER BY Id DESC LIMIT 1", Account.LocalAccountId, kind).FirstOrDefault ();
  179 +
176 180 if (res != null){
177 181 // This should return one overlapping value.
178 182 since = res.Id - 1;
@@ -183,9 +187,6 @@ public override void ReloadTimeline ()
183 187
184 188 void DownloadTweets (int insertPoint, long? since, long? max_id, Element removeOnInsert)
185 189 {
186   - //if (kind != TweetKind.Home)
187   - // return;
188   -
189 190 Account.ReloadTimeline (kind, since, max_id, count => {
190 191 mainSection.Remove (removeOnInsert);
191 192 if (count == -1){
@@ -201,7 +202,13 @@ void DownloadTweets (int insertPoint, long? since, long? max_id, Element removeO
201 202 long lastId = GetTableTweetId (insertPoint == 0 ? 0 : insertPoint-1) ?? 0;
202 203
203 204 continuous = false;
204   - int nParsed = mainSection.Insert (insertPoint, UITableViewRowAnimation.None, FetchTweets (count, lastId, insertPoint));
  205 + int nParsed;
  206 +
  207 + Util.ReportTime ("Before Loading tweet from DB");
  208 + lock (Database.Main)
  209 + nParsed = mainSection.Insert (insertPoint, UITableViewRowAnimation.None, UnlockedFetchTweets (count, lastId, insertPoint));
  210 + Util.ReportTime ("Time spent loading tweets");
  211 +
205 212 NavigationController.TabBarItem.BadgeValue = (nParsed > 1) ? nParsed.ToString () : null;
206 213
207 214 if (!continuous){
@@ -228,9 +235,10 @@ void DownloadTweets (int insertPoint, long? since, long? max_id, Element removeO
228 235
229 236 protected override void ResetState ()
230 237 {
231   - mainSection = new Section () {
232   - FetchTweets (200, 0, 0)
233   - };
  238 + lock (Database.Main)
  239 + mainSection = new Section () {
  240 + UnlockedFetchTweets (200, 0, 0)
  241 + };
234 242
235 243 Root = new RootElement (timelineTitle) {
236 244 UnevenRows = true
20 TweetStation/UI/User.cs
@@ -95,15 +95,17 @@ public StreamedUserViewController (string title, string url, User reference) : b
95 95
96 96 protected override void PopulateRootFrom (byte [] result)
97 97 {
98   - Database.Main.Execute ("BEGIN");
99   - var userStream = User.LoadUsers (new MemoryStream (result));
100   -
101   - Root = new RootElement (StreamedTitle){
102   - new Section () {
103   - from user in userStream select (Element) new UserElement (user)
104   - }
105   - };
106   - Database.Main.Execute ("END");
  98 + lock (Database.Main){
  99 + Database.Main.Execute ("BEGIN");
  100 + var userStream = User.UnlockedLoadUsers (new MemoryStream (result));
  101 +
  102 + Root = new RootElement (StreamedTitle){
  103 + new Section () {
  104 + from user in userStream select (Element) new UserElement (user)
  105 + }
  106 + };
  107 + Database.Main.Execute ("END");
  108 + }
107 109 }
108 110 }
109 111

0 comments on commit 43c664d

Please sign in to comment.
Something went wrong with that request. Please try again.