Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • Sep 01 2018 06:46
    hosseingholizadeh opened #72
  • Aug 25 2018 21:19
    gregoriusus commented #66
  • Aug 25 2018 21:18
    gregoriusus closed #50
  • Feb 05 2018 17:43
    ti24horas synchronize #66
  • Oct 12 2017 13:00
    frankaxela opened #71
  • Sep 13 2017 21:15
    ThomasMahoney1959 edited #70
  • Sep 13 2017 21:13
    ThomasMahoney1959 opened #70
  • Aug 29 2017 10:13
    danbarua commented #69
  • Aug 25 2017 20:46
    Reltik opened #69
  • Jan 13 2017 12:46

    danbarua on master

    Update appveyor.yml Add myget … (compare)

  • Jan 11 2017 22:27

    danbarua on master

    re #68 cancel network read on s… (compare)

  • Jan 11 2017 22:20
    danbarua opened #68
  • Dec 19 2016 10:04
    tdragon-fp commented #67
  • Dec 19 2016 10:01
    tdragon-fp commented #67
  • Dec 19 2016 09:36
    danbarua commented #67
  • Dec 19 2016 08:51
    tdragon-fp edited #67
  • Dec 19 2016 08:39
    tdragon-fp opened #67
  • Nov 30 2016 12:33
    ti24horas commented #66
  • Nov 29 2016 21:34
    danbarua commented #66
  • Nov 29 2016 21:27
    ti24horas opened #66
Armin
@pragmatrix
Just to be clear, in the example above, the Play() and the Hangup() could throw an exception when the socket dies (and for several other reasons), because IsConnected (asynchronous in nature) represents old state at the time they are called. So why do the IsConnected checks at all? Client code needs to handle that situation at all times anyway.
Dan Barua
@danbarua
In this case i'm doing it inside a catch() block and I find nested try/catch blocks ugly :trollface:
Of course, it's all open to debate and refinement
I was getting nasty NullReferenceExceptions sometimes in my error handler
Armin
@pragmatrix
Of course this depends on the error recovering strategy, we are killing the complete call, but we do manage only one call /bridge per process.
Dan Barua
@danbarua
One call/bridge per .exe process?
Armin
@pragmatrix
yes
Dan Barua
@danbarua
Ah ok in mine I'm handling all-the-things
Armin
@pragmatrix
This explains it a lot. We can tolerate more crashes and learn from them.
So as long FreeSwitch does not leave us....
Dan Barua
@danbarua
Yep, I think I've seen only one violent disconnection in about a month
We need to act on this to remove a call from a contact centre queue so it does not hold up other calls
Armin
@pragmatrix
Annoying enough, I do understand.
Dan Barua
@danbarua
I think the idea of the RunIfAnswered check was to essentially allow application code to 'fall through' to the end quickly
Armin
@pragmatrix
Can "IsAnswered" go to false asynchronously?
Dan Barua
@danbarua
Yeah - it's always updated using the latest channel event from freeswitch
So you might have some code that's playing audio prompts or whatever
and separately you'd have a hangup handler attached to do any clean up on hangup
if we get a hangup, the audio prompts will just fall through and complete
while the hangup handler reacts to the hangup
I'm finding you need to think functionally and reactive with this
eg. I was doing a bridge, and trying to do some operations on Channel.OtherLeg as soon as the bridge was completed
But you might get to that code before Channel.OtherLeg has been set
The reactive way to do it is on the BridgedChannels observable
eg, i'm porting some legacy stuff off another platform to FreeSwitch/NEventSocket
channel.BridgedChannels.Take(1).Subscribe(
    async c =>
    {
        //the first bridged-channel will have feature codes enabled on it
        NiceLog(LogLevel.Debug, "Channel Bridged");
        c.FeatureCodes().Subscribe(async x => await HandleFeatureCodes(x));

        c.HangupCallBack = async e =>
        {
            NiceLog(
                LogLevel.Debug,
                $"Hangup on bridged channel, setting phone {queueStatus.PhoneId} off call");
            await database.Exec(new PhoneSetOffCall((int)queueStatus.PhoneId, 16));
        };

        await c.SetChannelVariable("record_append", "true");
        await c.StartRecording(recordingPath);
    });

channel.BridgedChannels.Skip(1).Subscribe(
    async c =>
    {
        //subsequent bridged channels will call Agent_ReleaseByURN on hangup,
        //this is in case the
        //call is transferred to someone using the agent app and we need to unlock them
        NiceLog(LogLevel.Debug, "Transfer Channel Bridged");
        c.HangupCallBack = async e => await database.Exec(new ReleaseByURN((int)Urn));

        //can't do more than one transfer or database will blow up when we try to log it
        wasTransferred = true;

        if (recordingFollowTransfer)
        {
            //start recording on c-leg
            await c.SetChannelVariable("record_append", "true");
            await c.StartRecording(recordingPath);
        }
    });
Armin
@pragmatrix
I see, coordination here is a pain, at least it seems to me, specifically in the context of exceptions happening everywhere.
Dan Barua
@danbarua
Yeah, exceptions are the big pain point
What I want to do is ideally have as few try/catch blocks, especially nested ones, and do the right thing as much as possible
So inside my .Subscribe() callbacks I'm handling OperationCanceledException - this is when I try to do something to a channel which is disconnected
Inside my database code I'm using Polly for re-try/backoff behaviour
and then my top-level exception handler just looks like this
This message was deleted
catch (Exception ex)
{
    Log.Error(ex, $"Error handling call on channel {channel.UUID}");

    try
    {
        ChannelHandler handler;
        //we got disconnected before receiving a hangup event, clean up
        if (calls.TryRemove(channel.UUID, out handler))
        {
            await database.Exec(new Dequeue(handler.UnitId, handler.LineId, (int)DequeueReason.ManualDequeue));
            if (handler.QueueStatus?.PhoneId != null) await database.Exec(new PhoneSetOffCall((int)handler.QueueStatus.PhoneId.Value, 16));
        }

        if (channel.IsAnswered && channel.Socket?.IsConnected == true)
        {
            await channel.Play("misc/error.wav".PromptUrl());
            await Task.Delay(100);
            await channel.Hangup(HangupCause.NormalTemporaryFailure);
        }
    }
    catch (Exception hangupException)
    {
        Log.Warn(hangupException, "Error hangup up while handling an error");
    }
}
calls is just a concurrent dictionary -> we expose that via an HTTP interface as a JSON view model that you can control via http requests
As you can see, coordination/error handling is tricky
Armin
@pragmatrix
That's why I build this F# IVR library. Let's see how this turns out.
Dan Barua
@danbarua
The code started out nice and clean until I had to start dealing with exceptions!
Armin
@pragmatrix
Yes, the key here is to define clear "execution" boundaries, but Rx is confusing me a lot there, because everything can happen later in time.
Dan Barua
@danbarua
Well the system we're porting off uses a proprietary scripting language that looks the red-headed lovechild of BASIC and PASCAL
So this is way, way cleaner, if not perfect yet
Armin
@pragmatrix
nb: we will begin testing next week. Port from Asterisk to FreeSwitch is not nearly complete.
We port from Delphi :)
not => now
Dan Barua
@danbarua
This thing has all-global variables
so everything has to be namespaced for safety
ah I'll stop there because I could rant about this for hours
Armin
@pragmatrix
me, too. Let's do some work :)
Daniel Radostev
@dradostev
Hello. Does this library work properly under .NET Core 2.0?
Dale Pitman
@GrumpyDeveloper007
Greetings, is this project still active?