danbarua / NEventSocket

A reactive FreeSwitch eventsocket library for Modern .Net
Mozilla Public License 2.0
74 stars 37 forks source link

BridgeTo hangs when called multiple times #62

Open mguerrieri opened 7 years ago

mguerrieri commented 7 years ago

I am able to reproduce this issue against the stable and latest codebases. Open an inbound socket and originate to a phone. Using Channel API, get channel for successfully originated call. Wait 10 seconds and call BridgeTo to bridge to a different phone number. Bridge is successful. Hangup bridged phone, and detect the hangup in Channel API. Wait 10 seconds and call BridgeTo to bridge to the phone number again. Bridge is successful. Wait 10 seconds and call BridgeTo a 3rd time. BridgeTo hangs and never returns, fs_cli shows no indication that it received the bridge command. Channel remains hung until you hangup the originally originated call, then BridgeTo returns and fs_cli shows the bridge command failing (obviously) because of the hangup. I have tried this countless times and it always hangs on the 3rd BridgeTo. I also tried setting the event-lock option on the bridge (as recommended by the FS docs for an outbound socket bridge, but it made no difference. Also, my original version of the code used only the inbound socket and no Channel API, but I had the same problem (which prompted me to try outbound sockets/Channel API).

This is a showstopper for me and I am at a loss, not being very fluent in the reactive extensions (I can only debug so far before I get in over my head). Any help is greatly appreciated.

Below is a console app to reproduce (.NET Core console app)- obviously substitute your own FS config and phone numbers.

Dial Plan:

<extension name="bridge_test">
  <condition field="destination_number" expression="^5557$">
      <action application="set" data="park_after_bridge=true" />
      <action application="pre_answer" />
      <action application="playback" data="{loops=2}tone_stream://%(300,25,440)" />
      <action application="answer" />
      <action application="socket" data="127.0.0.1:8084 async full" />
  </condition>
</extension>

project.json:

{
  "version": "1.0.0-*",
  "buildOptions": {
    "emitEntryPoint": true
  },

  "dependencies": {
    "NETStandard.Library": "1.6.0",
    "NEventSocket": "1.1.0",
    "Rx-Main": "2.2.5"
  },

  "frameworks": {
    "net452": {
    }
  }
}

Program.cs:

using NEventSocket;
using NEventSocket.Channels;
using NEventSocket.FreeSwitch;
using System;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace FSBridgeTest
{
    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                var cancellationTokenSource = new CancellationTokenSource();
                Console.WriteLine("Starting bridge test- hit any key abort...");
                var task = RunBridgeTest(cancellationTokenSource.Token);
                Console.ReadKey();
                Console.WriteLine("Shutting down...");
                cancellationTokenSource.Cancel();
                Console.WriteLine("Shutdown complete.");
                task.Wait();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                Console.WriteLine("Hit any key exit...");
                Console.ReadKey();
            }
        }

        static async Task<bool> BridgeToPhone(Channel station, string phone)
        {
            var lineUUID = Guid.NewGuid().ToString("N");

            var bridgeOptions = new BridgeOptions
            {
                CallerIdNumber = "1003",
                CallerIdName = "Bridge Test",
                HangupAfterBridge = true,
                IgnoreEarlyMedia = false,
                TimeoutSeconds = 30,
                UUID = lineUUID
            };

            string endpoint = $"sofia/gateway/my-gateway/{phone}";

            Console.WriteLine($"Bridging {phone} to station channel {station.UUID}...");

            await station.BridgeTo(
                endpoint,
                bridgeOptions);

            if (!station.IsBridged)
            {
                Console.WriteLine($"Bridge failed.");
                return false;
            }
            else
            {
                Console.WriteLine($"Bridge successful.");
                return true;
            }
        }

        static Task RunBridgeTest(CancellationToken token)
        {
            var bridgeTestCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);

            return Task.Run(async () =>
            {
                try
                {
                    Console.WriteLine("Bridge test starting...");

                    using (var client = await InboundSocket.Connect())
                    {
                        using (var listener = new OutboundListener(8084))
                        {
                            listener.Channels.Subscribe(
                                async station =>
                                {
                                    try
                                    {
                                        Console.WriteLine($"Outbound socket connected for station channel {station?.UUID}.");

                                        station.HangupCallBack = (evt) =>
                                        {
                                            var hangupCause = evt.HangupCause?.ToString() ?? "Unknown";
                                            Console.WriteLine($"Station hangup detected for station channel {station?.UUID}- {hangupCause}.");
                                        };

                                        station.BridgedChannels.Subscribe(
                                            line =>
                                            {
                                                try
                                                {
                                                    Console.WriteLine($"Outbound socket connected for line channel {line?.UUID}.");

                                                    // Handle line hangup.
                                                    line.HangupCallBack = async (evt) =>
                                                        {
                                                            var hangupCause = evt.HangupCause?.ToString() ?? "Unknown";
                                                            Console.WriteLine($"Line hangup detected for line channel {line?.UUID}- {hangupCause}.");

                                                            await Task.Delay(10000);
                                                            await BridgeToPhone(station, "12155551212"); // Your cellphone here
                                                        };
                                                }
                                                catch (OperationCanceledException)
                                                {
                                                    Console.WriteLine($"Outbound socket disconnected for line channel {line?.UUID}.");
                                                }
                                            });

                                        await Task.Delay(10000);
                                        await BridgeToPhone(station, "12155551212"); // Your cellphone here.
                                    }
                                    catch (OperationCanceledException)
                                    {
                                        Console.WriteLine($"Outbound socket disconnected for station channel {station?.UUID}.");
                                    }
                                });

                            listener.Start();

                            var stationUUID = Guid.NewGuid().ToString("N");
                            var originateOptions = new OriginateOptions
                            {
                                CallerIdNumber = "1003",
                                CallerIdName = $"Station 1003",
                                HangupAfterBridge = false,
                                TimeoutSeconds = 20,
                                UUID = stationUUID
                            };

                            Console.WriteLine($"Originating to station channel {stationUUID}.");
                            var originate = await client.Originate(
                                "user/1003",
                                "5557",
                                "XML",
                                "default",
                                originateOptions);

                            if (!originate.Success)
                            {
                                var hangupCause = originate.HangupCause?.ToString() ?? "Unknown";
                                throw new Exception($"Originate failed: {hangupCause}");
                            }

                            Console.WriteLine($"Origination to station channel {stationUUID} complete.");

                            await Task.Delay(Timeout.Infinite, bridgeTestCancellationTokenSource.Token).ConfigureAwait(false);
                        }
                    }
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }

                Console.WriteLine("Bridge test complete.");
            });
        }
    }
}
mguerrieri commented 7 years ago

Think you referenced the wrong issue above- should have referenced issue #60, this is a different issue.

danbarua commented 7 years ago

Yep, I fixed the commit and force-pushed but the link is still in the Github UI.

re: this one, I'm trying to re-work it into an example that does what you want.

mguerrieri commented 7 years ago

Any thoughts as to why it hangs as-is?

mguerrieri commented 7 years ago

I think I may have this figured out. The

<action application="set" data="park_after_bridge=true" />

in the dialplan seems to be the culprit. I think this is needed for an inbound socket app to keep the channel alive after a bridge, but for an outbound channel app, it seems that the channel remains under the outbound socket's control after the bridge ends. Having park_after_bridge set causes the fist bridge to succeed, and park is executed when it ends. The next bridge works under the context of the park application, which ends when the bridge ends, which appears to leave the channel in a hung state. Bottom line is that is does not appear that park plays well with outbound sockets (at least in my scenario).

danbarua commented 7 years ago

That would make sense. Some dialplan applications are blocking and will not yield control of the channel until finished. Have you tried setting HangUpAfterBridge = false on the bridge call? It works with outbound sockets. Also, all the stuff you've done in dialplan XML you should be able to do with the InboundSocket.

mguerrieri commented 7 years ago

The FreeSWITCH docs state that false is the default value for hangup_after_bridge, so I just didn’t set it at all. As for the dialplan stuff, it is there just because we want to give control of things like whether to play a tone on connect and what type of tone, etc. to an end-user who can make those changes without knowing anything about the socket code, but yeah, I could (and previously did) use the InboundSocket to answer/play tone/park, etc.

From: Dan Barua [mailto:notifications@github.com] Sent: Tuesday, September 20, 2016 4:54 AM To: danbarua/NEventSocket NEventSocket@noreply.github.com Cc: Mark Guerrieri mguerrieri@jmbtech.net; Author author@noreply.github.com Subject: Re: [danbarua/NEventSocket] BridgeTo hangs when called multiple times (#62)

That would make sense. Some dialplan applications are blocking and will not yield control of the channel until finished. Have you tried setting HangUpAfterBridge = false on the bridge call? It works with outbound sockets. Also, all the stuff you've done in dialplan XML you should be able to do with the InboundSocket.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/danbarua/NEventSocket/issues/62#issuecomment-248243036 , or mute the thread https://github.com/notifications/unsubscribe-auth/AEGPVVXNM47FLBHnNAYKlmoVKziHnzqyks5qr58TgaJpZM4KANY7 . https://github.com/notifications/beacon/AEGPVdS2aPcHmTGTo5Id6g1PilVV5Fytks5qr58TgaJpZM4KANY7.gif