Re: [Evolution-hackers] New implementation of camel_imap_folder_fetch_data

2007-01-08 Thread Philip Van Hoof

So, after the lock fixes and a few other bugfixes, this is a new
implementation.

I left the partial-retrieval thingy in place. Just remove the "full"
parameter from the function and comment the else part of the "if (full)
{ } else { }" thingy to use it in a normal Camel.


static void 
handle_freeup (CamelImapStore *store, gint nread, CamelException *ex)
{
if (nread <= 0) 
{
if (errno == EINTR)
camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, 
_("Operation cancelled"));
else
camel_exception_setv (ex, 
CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
  _("Server unexpectedly 
disconnected: %s"),
  g_strerror (errno));

camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
}
}

CamelStream *
camel_imap_folder_fetch_data (CamelImapFolder *imap_folder, const char *uid,
  const char *section_text, gboolean cache_only,
  gboolean full, CamelException *ex)
{
CamelFolder *folder = CAMEL_FOLDER (imap_folder);
CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
CamelStream *stream;

/* EXPUNGE responses have to modify the cache, which means
 * they have to grab the cache_lock while holding the
 * connect_lock.

 * Because getting the service lock may cause MUCH unecessary
 * delay when we already have the data locally, we do the
 * locking separately.  This could cause a race
 * getting the same data from the cache, but that is only
 * an inefficiency, and bad luck.
 */

CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
stream = camel_imap_message_cache_get (imap_folder->cache, uid, 
section_text, ex);

/* TNY TODO: if (full) Detect retrieval status (if partial refetch) */

if (!stream && (!strcmp (section_text, "HEADER") || !strcmp 
(section_text, "0"))) 
{
camel_exception_clear (ex);
stream = camel_imap_message_cache_get (imap_folder->cache, uid, 
"", ex);
}
CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);

if (stream || cache_only)
return stream;

camel_exception_clear(ex);

CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);

if (!camel_imap_store_connected(store, ex)) {
camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
 _("This message is not currently 
available"));
CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
return NULL;
}

camel_exception_clear (ex);

stream = camel_imap_message_cache_insert (imap_folder->cache, 
uid, full?section_text:"", "", 0, NULL);
if (stream == NULL)
stream = camel_stream_mem_new ();

if (!stream)
goto errorhander;

if (full)
{
gboolean first = TRUE, err=FALSE;
gchar line[512];
guint linenum = 0;
ssize_t nread; 
CamelStreamBuffer *server_stream = CAMEL_STREAM_BUFFER 
(store->istream);
gchar *tag;
guint taglen;
gboolean isnextdone = FALSE;

if (store->server_level < IMAP_LEVEL_IMAP4REV1 && !*section_text)
camel_imap_command_start (store, folder, ex,
"UID FETCH %s RFC822.PEEK",uid);
else
camel_imap_command_start (store, folder, ex,
"UID FETCH %s BODY.PEEK[%s]",uid, section_text);

tag = g_strdup_printf ("%c%.5u", store->tag_prefix, 
store->command-1);
taglen = strlen (tag);
store->command++;

while (nread = camel_stream_buffer_gets (server_stream, line, 512) 
> 0)
{

/* It might be the line before the last line */
if (line[0] == ')' && (line[1] == '\n' || (line[1] == '\r' 
&& line[2] == '\n')))
{
isnextdone = TRUE;
continue;
}

/* It's the first line */
if (linenum == 0 && (line [0] != '*' || line[1] != ' '))
{
err=TRUE;
break;
} else if (linenum == 0) { linenum++; continue; }

/* It's the last line */
if (!strncmp (line, tag, taglen))
break;

camel_seekable_stream_seek (CAMEL_SEEKABLE_STREAM (stream), 
0, CAMEL_STREAM_END);

if (isnextdone)
{
  

Re: [Evolution-hackers] New implementation of camel_imap_folder_fetch_data

2007-01-08 Thread Philip Van Hoof

Ah, I figured it out. The camel_imap_command_start places the lock, and
the functions that are usually used with the results unlock it.

owk. 

"* On success, the store's connect_lock will be locked. It will be freed
 * when you call camel_imap_response_free. (The lock is recursive, so
 * callers can grab and release it themselves if they need to run
 * multiple commands atomically.) "


Easy == different


On Tue, 2007-01-09 at 03:47 +0100, Philip Van Hoof wrote:
> Bweachrgg. It seems camel_imap_command_response performs an extra
> CAMEL_SERVICE_REC_UNLOCK (store, connect_lock) (for some reason, I can't
> figure out why that it is like that, but ... ).
> 
> And it looks that the code calling camel_imap_folder_fetch_data assumes
> that this unlock happens (at least at some obscure places).
> 
> In other words: crazy code. Anyway, adding that extra unlock seems to
> fix it (the current implementation below caused some races here).
> 
> Just add an extra CAMEL_SERVICE_REC_UNLOCK (store, connect_lock) before
> the returning of the stream and in the errorhandler label.
> 
> b
> 
> ps. The locking code of the imap provider is really, really broken or
> done in an absolutely insane way.
> 
> 
> On Tue, 2007-01-09 at 02:18 +0100, Philip Van Hoof wrote:
> > Hi there,
> > 
> > I just finished this one, so there's probably a huge amount of bugs in
> > it. I know I should first debug them away before posting on mailing
> > lists ;). However, it would be nice if some (other) people would test
> > this reimplementation (it's a method in camel-imap-folder.c, by the
> > way).
> > 
> > Just take a look at it, compare it with the memory consumption of
> > copying everything into a GData after first copying everything into a
> > GPtrArray of a CamelImapResponse structure (which is what the original
> > does).
> > 
> > ps. the if (TRUE) {} thing is because in the version for tinymail,
> > there's support for partially retrieving messages. The else part
> > implements that support (feel free to take a look at tinymail's
> > camel-lite for the implementation, it's not a secret or something).
> > 
> > 
> > CamelStream *
> > camel_imap_folder_fetch_data (CamelImapFolder *imap_folder, const char *uid,
> >   const char *section_text, gboolean cache_only,
> >   CamelException *ex)
> > {
> > CamelFolder *folder = CAMEL_FOLDER (imap_folder);
> > CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
> > CamelStream *stream;
> > 
> > /* EXPUNGE responses have to modify the cache, which means
> >  * they have to grab the cache_lock while holding the
> >  * connect_lock.
> > 
> >  * Because getting the service lock may cause MUCH unecessary
> >  * delay when we already have the data locally, we do the
> >  * locking separately.  This could cause a race
> >  * getting the same data from the cache, but that is only
> >  * an inefficiency, and bad luck.
> >  */
> > 
> > CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
> > stream = camel_imap_message_cache_get (imap_folder->cache, uid, 
> > section_text, ex);
> > 
> > /* TNY TODO: if (full) Detect retrieval status (if partial refetch) */
> > 
> > if (!stream && (!strcmp (section_text, "HEADER") || !strcmp 
> > (section_text, "0"))) 
> > {
> > camel_exception_clear (ex);
> > stream = camel_imap_message_cache_get (imap_folder->cache, uid, 
> > "", ex);
> > }
> > CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
> > 
> > if (stream || cache_only)
> > return stream;
> > 
> > camel_exception_clear(ex);
> > 
> > CAMEL_SERVICE_REC_LOCK (store, connect_lock);
> > CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
> > 
> > if (!camel_imap_store_connected(store, ex)) {
> > camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
> >  _("This message is not currently 
> > available"));
> > CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
> > CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
> > return NULL;
> > }
> > 
> > camel_exception_clear (ex);
> > 
> > stream = camel_imap_message_cache_insert (imap_folder->cache, 
> > uid, full?section_text:"", "", 0, NULL);
> > if (stream == NULL)
> > stream = camel_stream_mem_new ();
> > 
> > if (!stream)
> > goto errorhander;
> > 
> > if (TRUE)
> > {
> > gboolean first = TRUE;
> > gchar line[512];
> > guint linenum = 0;
> > ssize_t nread; 
> > CamelStreamBuffer *server_stream = CAMEL_STREAM_BUFFER 
> > (store->istream);
> > gchar *tag;
> > guint taglen;
> > gboolean isnextdone = FALSE;
> > 
> > if (store->server_level < IMAP_LEVEL_IMAP4REV1 && !*section_text)
> > camel_imap_command_start (stor

Re: [Evolution-hackers] New implementation of camel_imap_folder_fetch_data

2007-01-08 Thread Philip Van Hoof

Bweachrgg. It seems camel_imap_command_response performs an extra
CAMEL_SERVICE_REC_UNLOCK (store, connect_lock) (for some reason, I can't
figure out why that it is like that, but ... ).

And it looks that the code calling camel_imap_folder_fetch_data assumes
that this unlock happens (at least at some obscure places).

In other words: crazy code. Anyway, adding that extra unlock seems to
fix it (the current implementation below caused some races here).

Just add an extra CAMEL_SERVICE_REC_UNLOCK (store, connect_lock) before
the returning of the stream and in the errorhandler label.

b

ps. The locking code of the imap provider is really, really broken or
done in an absolutely insane way.


On Tue, 2007-01-09 at 02:18 +0100, Philip Van Hoof wrote:
> Hi there,
> 
> I just finished this one, so there's probably a huge amount of bugs in
> it. I know I should first debug them away before posting on mailing
> lists ;). However, it would be nice if some (other) people would test
> this reimplementation (it's a method in camel-imap-folder.c, by the
> way).
> 
> Just take a look at it, compare it with the memory consumption of
> copying everything into a GData after first copying everything into a
> GPtrArray of a CamelImapResponse structure (which is what the original
> does).
> 
> ps. the if (TRUE) {} thing is because in the version for tinymail,
> there's support for partially retrieving messages. The else part
> implements that support (feel free to take a look at tinymail's
> camel-lite for the implementation, it's not a secret or something).
> 
> 
> CamelStream *
> camel_imap_folder_fetch_data (CamelImapFolder *imap_folder, const char *uid,
> const char *section_text, gboolean cache_only,
> CamelException *ex)
> {
>   CamelFolder *folder = CAMEL_FOLDER (imap_folder);
>   CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
>   CamelStream *stream;
>   
>   /* EXPUNGE responses have to modify the cache, which means
>* they have to grab the cache_lock while holding the
>* connect_lock.
> 
>* Because getting the service lock may cause MUCH unecessary
>* delay when we already have the data locally, we do the
>* locking separately.  This could cause a race
>* getting the same data from the cache, but that is only
>* an inefficiency, and bad luck.
>*/
> 
>   CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
>   stream = camel_imap_message_cache_get (imap_folder->cache, uid, 
> section_text, ex);
> 
>   /* TNY TODO: if (full) Detect retrieval status (if partial refetch) */
> 
>   if (!stream && (!strcmp (section_text, "HEADER") || !strcmp 
> (section_text, "0"))) 
>   {
>   camel_exception_clear (ex);
>   stream = camel_imap_message_cache_get (imap_folder->cache, uid, 
> "", ex);
>   }
>   CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
>   
>   if (stream || cache_only)
>   return stream;
> 
>   camel_exception_clear(ex);
> 
>   CAMEL_SERVICE_REC_LOCK (store, connect_lock);
>   CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
> 
>   if (!camel_imap_store_connected(store, ex)) {
>   camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
>_("This message is not currently 
> available"));
>   CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
>   CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
>   return NULL;
>   }
>   
>   camel_exception_clear (ex);
> 
> stream = camel_imap_message_cache_insert (imap_folder->cache, 
>   uid, full?section_text:"", "", 0, NULL);
>   if (stream == NULL)
>   stream = camel_stream_mem_new ();
> 
>   if (!stream)
>   goto errorhander;
> 
>   if (TRUE)
>   {
>   gboolean first = TRUE;
>   gchar line[512];
>   guint linenum = 0;
>   ssize_t nread; 
>   CamelStreamBuffer *server_stream = CAMEL_STREAM_BUFFER 
> (store->istream);
>   gchar *tag;
>   guint taglen;
>   gboolean isnextdone = FALSE;
> 
>   if (store->server_level < IMAP_LEVEL_IMAP4REV1 && !*section_text)
>   camel_imap_command_start (store, folder, ex,
>   "UID FETCH %s RFC822.PEEK",uid);
>   else
>   camel_imap_command_start (store, folder, ex,
>   "UID FETCH %s BODY.PEEK[%s]",uid, section_text);
> 
>   tag = g_strdup_printf ("%c%.5u", store->tag_prefix, 
> store->command-1);
>   taglen = strlen (tag);
>   store->command++;
> 
>   while (nread = camel_stream_buffer_gets (server_stream, line, 512) 
> > 0)
>   {
> 
>   /* It might be the line before the last line */
>   if 

[Evolution-hackers] New implementation of camel_imap_folder_fetch_data

2007-01-08 Thread Philip Van Hoof
Hi there,

I just finished this one, so there's probably a huge amount of bugs in
it. I know I should first debug them away before posting on mailing
lists ;). However, it would be nice if some (other) people would test
this reimplementation (it's a method in camel-imap-folder.c, by the
way).

Just take a look at it, compare it with the memory consumption of
copying everything into a GData after first copying everything into a
GPtrArray of a CamelImapResponse structure (which is what the original
does).

ps. the if (TRUE) {} thing is because in the version for tinymail,
there's support for partially retrieving messages. The else part
implements that support (feel free to take a look at tinymail's
camel-lite for the implementation, it's not a secret or something).


CamelStream *
camel_imap_folder_fetch_data (CamelImapFolder *imap_folder, const char *uid,
  const char *section_text, gboolean cache_only,
  CamelException *ex)
{
CamelFolder *folder = CAMEL_FOLDER (imap_folder);
CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
CamelStream *stream;

/* EXPUNGE responses have to modify the cache, which means
 * they have to grab the cache_lock while holding the
 * connect_lock.

 * Because getting the service lock may cause MUCH unecessary
 * delay when we already have the data locally, we do the
 * locking separately.  This could cause a race
 * getting the same data from the cache, but that is only
 * an inefficiency, and bad luck.
 */

CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
stream = camel_imap_message_cache_get (imap_folder->cache, uid, 
section_text, ex);

/* TNY TODO: if (full) Detect retrieval status (if partial refetch) */

if (!stream && (!strcmp (section_text, "HEADER") || !strcmp 
(section_text, "0"))) 
{
camel_exception_clear (ex);
stream = camel_imap_message_cache_get (imap_folder->cache, uid, 
"", ex);
}
CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);

if (stream || cache_only)
return stream;

camel_exception_clear(ex);

CAMEL_SERVICE_REC_LOCK (store, connect_lock);
CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);

if (!camel_imap_store_connected(store, ex)) {
camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
 _("This message is not currently 
available"));
CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
return NULL;
}

camel_exception_clear (ex);

stream = camel_imap_message_cache_insert (imap_folder->cache, 
uid, full?section_text:"", "", 0, NULL);
if (stream == NULL)
stream = camel_stream_mem_new ();

if (!stream)
goto errorhander;

if (TRUE)
{
gboolean first = TRUE;
gchar line[512];
guint linenum = 0;
ssize_t nread; 
CamelStreamBuffer *server_stream = CAMEL_STREAM_BUFFER 
(store->istream);
gchar *tag;
guint taglen;
gboolean isnextdone = FALSE;

if (store->server_level < IMAP_LEVEL_IMAP4REV1 && !*section_text)
camel_imap_command_start (store, folder, ex,
"UID FETCH %s RFC822.PEEK",uid);
else
camel_imap_command_start (store, folder, ex,
"UID FETCH %s BODY.PEEK[%s]",uid, section_text);

tag = g_strdup_printf ("%c%.5u", store->tag_prefix, 
store->command-1);
taglen = strlen (tag);
store->command++;

while (nread = camel_stream_buffer_gets (server_stream, line, 512) 
> 0)
{

/* It might be the line before the last line */
if (line[0] == ')' && (line[1] == '\n' || (line[1] == '\r' 
&& line[2] == '\n')))
{
isnextdone = TRUE;
continue;
}

/* It's the first line */
if (linenum == 0 && (line [0] != '*' || line[1] != ' '))
{
g_free (tag);
goto errorhander;
} else if (linenum == 0) { linenum++; continue; }

/* It's the last line */
if (!strncmp (line, tag, taglen))
break;

camel_seekable_stream_seek (CAMEL_SEEKABLE_STREAM (stream), 
0, CAMEL_STREAM_END);

if (isnextdone)
{
camel_stream