Opened 14 years ago

Closed 7 years ago

#1147 defect closed worksforme (worksforme)

twisted.mail.smtp.py timeout can cause runtime error

Reported by: xing Owned by:
Priority: low Milestone:
Component: mail Keywords:
Cc: Jean-Paul Calderone, xing, Andrii V. Mishkovskyi, argonemyth Branch:
Author:

Description


Change History (10)

comment:1 Changed 14 years ago by xing

Under Python2.4 and latest SVN twisted checkout.

Once in a while in my smtp app I would get the following.

Traceback (most recent call last):
  File "C:\Program Files\ActiveState Komodo 3.1\dbgp\pythonlib\dbgp\client.py",
line 590, in __init__
    execfile(file, globals, locals)
  File "E:\EPRESS\mail\dns_query.py", line 451, in ?
    reactor.run()
  File "E:\Python24\lib\site-packages\twisted\internet\posixbase.py", line 206,
in run
    self.mainLoop()
  File "E:\Python24\lib\site-packages\twisted\internet\posixbase.py", line 214,
in mainLoop
    self.runUntilCurrent()
--- <exception caught here> ---
  File "E:\Python24\lib\site-packages\twisted\internet\base.py", line 541, in
runUntilCurrent
    call.func(*call.args, **call.kw)
  File "E:\Python24\lib\site-packages\twisted\protocols\policies.py", line 468,
in __timedOut
    self.timeoutConnection()
  File "E:\Python24\lib\site-packages\twisted\mail\smtp.py", line 1010, in
timeoutConnection
    self.log))
  File "E:\Python24\lib\site-packages\twisted\mail\smtp.py", line 1524, in sendError
    self.factory.result.errback(exc)
exceptions.AttributeError: SMTPSenderFactory instance has no attribute 'result'

comment:2 Changed 14 years ago by Jean-Paul Calderone

How?

comment:3 Changed 14 years ago by xing

The following SendMail is adopted from the sendmail function withn smtp class.
The exception is captured in the included _ebError() at the "UNKNOWN" error
part. SMTP_TIMEOUT is set to 5 seconds.

def SendMail(self, smtphost, envid, msg):
        body = StringIO(msg.Compose(envid)) ##need to convert string into a
readable object for smtp
        #if not hasattr(body,'read'):
        #body = StringIO(str(msg))
            
        env = msg.Get_Envelope(envid)
         
        d = defer.Deferred()
        factory = smtp.SMTPSenderFactory(msg.From,[env.To],body, d, 0,
self.SMTP_TIMEOUT)
        
        (mta_ip, mta_hostname) = Mail_Core().Get_VirtualMTA()
        
        factory.domain = mta_hostname
        reactor.connectTCP(mta_ip, 25, factory, self.SMTP_TIMEOUT) 
        return d

   def _ebError(self, err, server, envid, msg):
        self.Runners -= 1
        self.ErrorCount += 1 
        
        if err.value.__class__ == smtp.SMTPDeliveryError:
            print '=== DELIVERY ERROR ==='
            print err.value.code
            print err.value.resp
            print err.value.log  
            self.MX_Group.Log_Success(server, err.value.code, err.value.resp)
            self.Queue_Defer(envid, msg.MessageID)
            reactor.callLater(self.DELIVERY_INTERVAL, self.Delivery_Looper)
        elif err.value.__class__ == smtp.SMTPTimeoutError:
            print '=== SLOW MX ==='
            print err.value.code
            print err.value.resp
            print err.value.log
            self.MX_Group.Log_Failure(server,  0 , ' server timed out')
            self.Queue_Requeue(envid, msg.MessageID)
            reactor.callLater(self.DELIVERY_INTERVAL, self.Delivery_Looper)
        #considered mx errrors..
        elif(err.value.__class__ == smtp.SMTPConnectError
            or err.value.__class__ == error.TCPTimedOutError
            or err.value.__class__ == error.TimeoutError
            or err.value.__class__ == error.ConnectionRefusedError
            or err.value.__class__ == error.ConnectionLost):
            
            print '=== None Responsive MX ==='
            self.MX_Group.Log_Failure(server, 0 , ' server timed out')
            self.Queue_Requeue(envid, msg.MessageID)
            reactor.callLater(self.DELIVERY_INTERVAL, self.Delivery_Looper)
            
        else:
            print '=== UNKNOWN ERROR:' + str(err.value.__class__) + ' ==='
            self.MX_Group.Log_Failure(server, 0 , ' unknown' +
str(err.value.__class__))
            err.printTraceback()

comment:4 Changed 14 years ago by xing

so my calling would be link..

d = self.SendMail(...)
d.addCallback(...)
d.addErrback(...)

My original pasted code has tab problems. The error is throw to my errback
capture of the result but I was expecting some time of error.XXXX error, not an
general run-time attribute error from the smtp class.

comment:5 Changed 13 years ago by Jean-Paul Calderone

Component: mail

comment:6 Changed 13 years ago by Jean-Paul Calderone

Priority: highlow

No luck reproducing this so far. In general, I don't even see how the errback you defined could be invoked with such an attribute error, since traceback indicates that the Deferred is nowhere to be found.

It looks like there might be a race between issuing RSET, resetting the timeout, and dropping the connection, but I'm not sure this is actually the case you're encountering.

If you can produce a self-contained failing example that I can run to reproduce the traceback, I may have more luck tracking down the problem. As a start, here's a program I wrote to try to produce the problem:

if __name__ == '__main__':
    import smtperror
    raise SystemExit(smtperror.main())

from sys import stdout
from StringIO import StringIO

from zope.interface import implements

from twisted.internet import reactor
from twisted.internet.defer import succeed
from twisted.python import log
from twisted.cred.portal import IRealm, Portal
from twisted.cred.checkers import AllowAnonymousAccess
from twisted.mail.smtp import IMessageDelivery, IMessage, SMTPFactory, sendmail

from twisted.internet.protocol import Factory
Factory.noisy = False

INITIAL = 0.04
DECREMENT = 0.0001
INCREMENT = 0.0001

class Message:
    implements(IMessage)

    def lineReceived(self, line):
        pass


    def eomReceived(self):
        return succeed(None)


    def connectionLost(self):
        pass



class Mailbox:
    implements(IMessageDelivery)

    def receivedHeader(self, helo, origin, recipients):
        return ''


    def validateTo(self, user):
        return Message


    def validateFrom(self, helo, origin):
        return origin



class Realm:
    implements(IRealm)

    def requestAvatar(self, avatarId, mind, *interfaces):
        return IMessageDelivery, Mailbox(), lambda: None



def runServer():
    realm = Realm()
    portal = Portal(realm, [AllowAnonymousAccess()])
    f = SMTPFactory(portal)
    reactor.listenTCP(12345, f)



def runClient(timeout):
    print 'Sending mail with timeout', timeout
    d = sendmail(
        '127.0.0.1',
        'testuser@example.com',
        ['testuser@example.org'],
        StringIO('Hello'),
        port=12345,
        timeout=timeout)

    def cbSendmail(ign):
        runClient(timeout - DECREMENT)

    def ebSendmail(err):
        log.err(err, "Sendmail failed")
        runClient(timeout + INCREMENT)

    d.addCallbacks(cbSendmail, ebSendmail)



def main():
    log.startLogging(stdout)
    runServer()
    runClient(INITIAL)
    reactor.run()
    return 0

comment:7 Changed 9 years ago by <automation>

Owner: Jean-Paul Calderone deleted

comment:8 Changed 9 years ago by Andrii V. Mishkovskyi

Cc: Andrii V. Mishkovskyi added

Any updates in successfuly reproducing this one? I couldn't reproduce this one too (using exarkun's example). Unless original submitter provides reproducable piece of code, I'd suggest closing this one.

comment:9 Changed 7 years ago by argonemyth

Cc: argonemyth added

Need suggestions on what to do with this ticket...should we close this one?

comment:10 Changed 7 years ago by argonemyth

Resolution: worksforme
Status: newclosed

Well, still can't re-produce the 'bug' after 7 years. I did modified the exarkun's script to use the SMTPSenderFactory, the current version of sendmail function no longer has the timeout parameter. If you guys couldn't re-produce it back then, I don't think it will ever be re-produced again, (due to the massive changes of the source code)...I am closing this ticket for good.

Note: See TracTickets for help on using tickets.