Hello,

I'm testing dovecot with some setups, and one of them is with shared
mailboxes. The test I wrote will create and delete mail using multiple
connections to the same user and folder. Each connection makes a couple of
mails, remembers the uid from APPENDUID, and will delete those emails again.
At the end of the test I expect an empty folder.

This is not what happens. At the end I still have several mails in the
folder. I lack insight in the dovecot source to tell exactly what's going
on. I've tested this with different setups:
1) local system user, connecting over localhost -> bug is present
2) local system user, connecting over internet -> bug is present, but is
harder to reproduce
3) dovecot as proxy to another imap server -> bug is present
In step 3, you can even setup a dovecot to be a proxy to another dovecot
server.

>From logging in the other imap server I've seen that a client command to the
proxy like:
TAG UID STORE 1:3 +FLAGS (\Deleted)
TAG UID EXPUNGE 1:3
will be sent to the other imap server in 3 steps, one for each message. When
running the test with multiple threads, that logging shows that some uids
are never sent to the other imap server, and some uids are sent over
different connections than they original were sent to. (Thread 1 deletes
1:3, Thread 2 deletes 4:6, the proxy of Thread 1 might expunge messages from
Thread 2 and vice versa).

Attached is a python script which tests the behavior. The script expects a
file named "testmail.eml" to upload to the imap server. I used an email
which was about 75 kB.
I tested using version: 2.2.22 (fe789d2).
Let me know if I can help in any other way too.

John

#!/usr/bin/python

import imaplib
import threading

folder = 'test'
threads = 10

username = 'user1'
password = 'pass'
hostname = 'localhost'
port = 143
ssl = False

def connect():
    if ssl:
        return imaplib.IMAP4_SSL(hostname, port)
    else:
        return imaplib.IMAP4(hostname, port)
    
def upload_message(conn, folder, rfc):
    stat, result = conn.append(folder, None, None, rfc)
    return conn.untagged_responses['APPENDUID'][-1].split(' ')[1]

def delete_messages(conn, folder, ids):
    stat, result = conn.uid('STORE', ids, '+FLAGS', '(\Deleted)')
    stat, result = conn.uid('EXPUNGE', ids)

def irritate():
    rfc = open('testmail.eml').read()
    conn = connect()
    # set to 4 to see all imap commands sent
    conn.debug = 0
    conn.login(username, password)
    conn.create(folder)
    conn.select(folder)
    batch = 3
    loop = 5
    for x in xrange(loop):
        print "[%s] starting loop %d" % (threading.current_thread().name, x)
        uids = []
        for y in xrange(batch):
            uid = upload_message(conn, folder, rfc)
            print "[%s] saved message %s" % (threading.current_thread().name, 
uid)
            uids.append(uid)
        ids = ','.join(uids)
        delete_messages(conn, folder, ids)
        print "[%s] deleted messages: %s" % (threading.current_thread().name, 
ids)
    conn.logout()

def cleanup():
    conn = connect()
    conn.login(username, password)
    conn.delete(folder)
    conn.logout()

def check_folder():
    conn = connect()
    conn.login(username, password)
    stat, result = conn.select(folder)
    folder_size = int(result[0])
    stat, result = conn.uid('FETCH', '1:*', '(FLAGS)')
    try:
        result.remove(None)
    except: pass
    if folder_size:
        print "Threading test went wrong, bug found"
    else:
        print "Threading test went ok, bug not found"
    print 'Folder size: %d, messages: %r' % (folder_size, result)
    conn.logout()

if __name__ == '__main__':
    cleanup()
    jobs = []
    for _ in xrange(threads):
        jobs.append(threading.Thread(target=irritate))
    for job in jobs:
        job.start()
    print '%d threads started, waiting for them to finish' % (len(jobs))
    for job in jobs:
        job.join()
    check_folder()

        
            

Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to