Exemple #1
0
 /// <summary>
 /// unlocks and close changes if needed (committing or aborting those)
 /// </summary>
 /// <param name="ll">the locklist to release</param>
 /// <param name="task">async task to indicate the completion of lock list replication</param>
 internal void IfNeededUnlockAllAndCloseChanges(ILockListTransaction ll, out Task task)
 {
     task = null;
     if (ll != null && ll.Complete(out task))
     {
         ll.Dispose();
     }
 }
Exemple #2
0
            /// <summary>
            /// Generates a poison pill.
            /// </summary>
            /// <param name="spec">the poison pill specification</param>
            /// <param name="session">the session where this poison pill needs to be executed</param>
            /// <param name="lockList">the lock list of the invocation</param>
            /// <returns>the result of applying the generated poison pill, or null if not allowed</returns>
            private RequestResponse GeneratePoisonPill(string spec, ClientSession session, ILockListTransaction lockList)
            {
                if (!ArePoisonPillAllowed)
                {
                    return(null);
                }

                try
                {
                    if (string.IsNullOrWhiteSpace(spec))
                    {
                        throw new ArgumentException("spec must have a content.");
                    }

                    if (session == null)
                    {
                        throw new ArgumentNullException(nameof(session));
                    }

                    int idx = spec.IndexOf(':');

                    if (idx == -1)
                    {
                        throw new FormatException("spec must be <action>:<path>");
                    }

                    string action = spec.Substring(0, idx);
                    string path   = spec.Substring(idx + 1);

                    this.backend.SetPoisonPillPath(path, action, session, lockList);

                    return(new RequestResponse()
                    {
                        CallId = 0,
                        Content = "poisonpill prepared",
                        ResponsePath = path,
                        ResultCode = (int)Code.Ok,
                    });
                }
                catch (FormatException e)
                {
                    return(new RequestResponse()
                    {
                        CallId = 0,
                        Content = "Exception while parsing poisonpill spec: " + e.Message,
                        ResponsePath = "/",
                        ResultCode = (int)Code.Badarguments,
                    });
                }
                catch (Exception e)
                {
                    return(new RequestResponse()
                    {
                        CallId = 0,
                        Content = "Exception while parsing poisonpill spec: " + e.Message,
                        ResponsePath = "/",
                        ResultCode = (int)Code.Apierror,
                    });
                }
            }
Exemple #3
0
            /// <summary>
            /// runs the command from the path
            /// </summary>
            /// <param name="req">the request containing the command</param>
            /// <param name="session">the session this command was run from</param>
            /// <param name="lockList">the lock list of the invocation</param>
            /// <returns>the repsonse to the command</returns>
            internal virtual RequestResponse RunCommand(RequestCreate req, ClientSession session, ILockListTransaction lockList)
            {
                if (req == null)
                {
                    throw new ArgumentNullException(nameof(req));
                }

                if (session == null)
                {
                    throw new ArgumentNullException(nameof(session));
                }

                MutableStat stat = new MutableStat(new FirstStat(0, MutableStat.ConvertTime(DateTime.UtcNow), 0));

                // we require the digest 'Commander'
                if (!string.Equals(session.Auth.ClientDigest, CommanderDigest, StringComparison.InvariantCultureIgnoreCase))
                {
                    return(new RequestResponse()
                    {
                        Content = "not executed",
                        ResponsePath = "command",
                        ResultCode = (int)Code.Authfailed,
                        Stat = stat,
                    });
                }

                string requestedCommand;
                object content;
                Code   result = this.RunCommandPath(req.Path, req.Data, session, lockList, out requestedCommand, out content);

                return(new RequestResponse()
                {
                    Content = content,
                    ResponsePath = requestedCommand,
                    ResultCode = (int)result,
                    Stat = stat,
                });
            }
Exemple #4
0
            /// <summary>
            /// runs the command encoded in the given path
            /// </summary>
            /// <param name="path">the command encoded in the path</param>
            /// <param name="data">optionally, the data associated with the request</param>
            /// <param name="session">Client session</param>
            /// <param name="lockList">the lock list of the invocation</param>
            /// <param name="requestedCommand">Requested command on return</param>
            /// <param name="content">More info on the command on return</param>
            /// <returns>code for possible error</returns>
            public Code RunCommandPath(string path, byte[] data, ClientSession session, ILockListTransaction lockList, out string requestedCommand, out object content)
            {
                if (path == null)
                {
                    throw new ArgumentNullException(nameof(path));
                }

                Code result = Code.Apierror;

                requestedCommand = "unknown";
                content          = "unknown command";

                if (!this.IsCommand(path))
                {
                    return(result);
                }

                string[] pieces = path.Split('/');

                if (pieces.Length >= 2)
                {
                    requestedCommand = pieces[1].ToLower();

                    if (!this.IsCommandAllowed(session, requestedCommand))
                    {
                        content = "command not allowed now";
                        return(Code.Apierror);
                    }

                    RingMasterEventSource.Log.RunCommand(requestedCommand);
                    this.backend.auditConsumer?.OnRunCommand(requestedCommand);

                    switch (requestedCommand)
                    {
                    case "getsetting":
                    {
                        content = RingMasterBackendCore.GetSetting(pieces[2]);
                        result  = Code.Ok;
                        break;
                    }

                    case "gc":
                    {
                        GC.Collect();
                        content = "GC scheduled";
                        result  = Code.Ok;
                        break;
                    }

                    case "takecheckpoint":
                    {
                        bool ok = this.backend.Factory.TakeCheckpoint();
                        content = string.Format("Checkpoint {0}scheduled", ok ? string.Empty : "not ");
                        result  = Code.Ok;
                        break;
                    }

                    case "buildtreefile":
                    {
                        string   commandResult;
                        string[] args = Encoding.UTF8.GetString(data).Split('|');
                        try
                        {
                            int             ver  = int.Parse(args[2]);
                            RequestResponse resp = this.backend.BuildTreeFile(nodepath: args[0], filename: args[1], version: ver);
                            commandResult = resp.ResultCode == (int)Code.Ok ? "success" : "failed";
                            result        = (Code)resp.ResultCode;
                        }
                        catch (Exception e)
                        {
                            commandResult = "failed:" + e.Message;
                            result        = Code.Systemerror;
                        }

                        content = string.Format("buildtreefile: nodepath={0} filename={1} result={2}", args[0], args[1], commandResult);

                        break;
                    }

                    case "mount":
                    {
                        string   commandResult;
                        string[] args = Encoding.UTF8.GetString(data).Split('|');
                        try
                        {
                            RequestResponse resp = this.backend.Mount(nodepath: args[0], filename: args[1], allowremount: false);
                            commandResult = resp.ResultCode == (int)Code.Ok ? "success" : "failed";
                            result        = (Code)resp.ResultCode;
                        }
                        catch (Exception e)
                        {
                            commandResult = "failed:" + e.Message;
                            result        = Code.Unknown;
                        }

                        content = string.Format("mount: nodepath={0} filename={1} result={2}", args[0], args[1], commandResult);

                        break;
                    }

                    case "remountroot":
                    {
                        string commandResult;
                        string filepath = Encoding.UTF8.GetString(data);
                        try
                        {
                            RequestResponse resp = this.backend.Mount(nodepath: "/", filename: filepath, allowremount: true, mountRoot: true);
                            commandResult = resp.ResultCode == (int)Code.Ok ? "success" : "failed";
                            result        = (Code)resp.ResultCode;
                        }
                        catch (Exception e)
                        {
                            commandResult = "failed:" + e.Message;
                            result        = Code.Unknown;
                        }

                        content = string.Format("mountroot: nodepath=/ filename={0} result={1}", filepath, commandResult);

                        break;
                    }

                    case "unmount":
                    {
                        string   commandResult;
                        string[] args = Encoding.UTF8.GetString(data).Split('|');

                        try
                        {
                            RequestResponse resp = this.backend.Unmount(args[0]);
                            commandResult = resp.ResultCode == (int)Code.Ok ? "success" : "failed";
                            result        = (Code)resp.ResultCode;
                        }
                        catch (Exception e)
                        {
                            commandResult = "failed:" + e.Message;
                            result        = Code.Unknown;
                        }

                        content = string.Format("mount: nodepath={0} result={1}", args[0], commandResult);

                        break;
                    }

                    case "remount":
                    {
                        string   commandResult;
                        string[] args = Encoding.UTF8.GetString(data).Split('|');
                        try
                        {
                            RequestResponse resp = this.backend.Mount(nodepath: args[0], filename: args[1], allowremount: true);
                            commandResult = resp.ResultCode == (int)Code.Ok ? "success" : "failed";
                            result        = (Code)resp.ResultCode;
                        }
                        catch (Exception e)
                        {
                            commandResult = "failed:" + e.Message;
                            result        = Code.Unknown;
                        }

                        content = string.Format("remount: nodepath={0} result={1}", args[0], commandResult);

                        break;
                    }

                    case "measure":
                    {
                        StringBuilder sb = new StringBuilder();

                        Dictionary <string, object> results;
                        try
                        {
                            this.backend.CanMeasure = true;
                            ((IUnsafeTreeAccess)this.backend).LockRootNoSync();

                            results = this.backend.MeasureFullTree(PlatformHelper.ProcessorCount, maxTimeForMeasurementInMillis);
                        }
                        finally
                        {
                            this.backend.CanMeasure = false;
                            ((IUnsafeTreeAccess)this.backend).ReleaseRoot();
                        }

                        if (results != null)
                        {
                            foreach (KeyValuePair <string, object> line in results)
                            {
                                string text;
                                string child_i = line.Key;

                                Exception e = line.Value as Exception;

                                if (e != null)
                                {
                                    text = string.Format("Metrics for {0}: {1}", child_i, e);
                                }
                                else
                                {
                                    ulong metrics = (ulong)line.Value;
                                    text = string.Format("Metrics for {0} is {1:X}", child_i, metrics);
                                }

                                sb.AppendLine(text);
                            }
                        }

                        content = sb.ToString();
                        result  = Code.Ok;
                        break;
                    }

                    case "getlocaltime":
                    {
                        content = string.Format("local time: " + DateTime.UtcNow.ToString("o"));
                        result  = Code.Ok;
                        break;
                    }

                    case "getcommands":
                    {
                        content = this.GetCommands();
                        result  = Code.Ok;
                        break;
                    }

                    case "trace":
                    {
                        string message = "<null>";
                        if (data != null)
                        {
                            message = Encoding.UTF8.GetString(data);
                        }

                        content = "traced: " + message;
                        result  = Code.Ok;
                        Trace.TraceInformation(message);
                        break;
                    }

                    case "failoverinto":
                    {
                        string replica = "<any>";
                        if (data != null)
                        {
                            replica = Encoding.UTF8.GetString(data);
                        }

                        content = "Failing over into: " + replica + " in 10 seconds";
                        this.backend.FailoverInto(replica, 10000);
                        result = Code.Ok;
                        break;
                    }

                    case "downloadurlintolocation":
                    {
                        string arguments = null;

                        if (data != null)
                        {
                            arguments = Encoding.UTF8.GetString(data);
                        }

                        // we run this one command asynchronously with any potential replication
                        bool hasStarted = this.backend.DownloadUrlIntoLocation(arguments);
                        if (hasStarted)
                        {
                            content = string.Format("DownloadUrlIntoLocation started arguments={0}", arguments);
                            result  = Code.Ok;
                        }
                        else
                        {
                            content = string.Format("DownloadUrlIntoLocation functionality is not implemented");
                            result  = Code.Unimplemented;
                        }

                        break;
                    }

                    case "poisonpill":
                    {
                        string spec = null;

                        if (data != null)
                        {
                            spec = Encoding.UTF8.GetString(data);
                        }

                        RequestResponse res = this.GeneratePoisonPill(spec, session, lockList);
                        if (res != null)
                        {
                            content = string.Format("poison pill executed. Result = {0}", res.Content);
                            result  = (Code)res.ResultCode;
                        }
                        else
                        {
                            content = "Poison pills not allowed from config";
                            result  = Code.Authfailed;
                        }

                        break;
                    }

                    case "importcheckpoint":
                    {
                        string location = null;

                        if (data != null)
                        {
                            location = Encoding.UTF8.GetString(data);
                        }

                        RingMasterThreadPool.Instance.QueueUserWorkItem(_ =>
                            {
                                Thread.Sleep(5000);
                                bool ok = this.backend.ImportCheckpoint(location);
                                Trace.TraceWarning("Import checkpoint {0}: {1}", location, ok ? "Succeeded" : "Failed");
                                if (ok)
                                {
                                    Trace.TraceWarning("This instance will die now");
                                    Environment.Exit(1);
                                }
                            });

                        content = "Importing checkpoint " + location + "file and restarting";
                        result  = Code.Ok;

                        break;
                    }

                    case "getstats":
                    {
                        lock (this.SyncObject)
                        {
                            content = string.Format("PersistentData.TotalNodes={0} PersistentData.TotalData={1} EphemeralData.TotalNodes={2} EphemeralData.TotalData={3}", this.backend.Factory.TotalNodes, this.backend.Factory.TotalData, this.backend.EphemeralFactory.TotalNodes, this.backend.EphemeralFactory.TotalData);
                        }

                        result = Code.Ok;

                        break;
                    }

                    case "dumpnodes":
                    {
                        bool scanEphemerals = pieces.Length > 2 && string.Equals(pieces[2], "scanephemerals", StringComparison.InvariantCultureIgnoreCase);
                        content = this.backend.Factory.DumpAllNodesForDebug();

                        if (scanEphemerals)
                        {
                            lock (this.SyncObject)
                            {
                                content = content + Environment.NewLine + "---Eph:" + Environment.NewLine + this.backend.ScanForEphemeral() + Environment.NewLine + "---";
                            }
                        }

                        result = Code.Ok;

                        break;
                    }

                    case "getverbositylevel":
                    {
                        content = "verbosity level is " + this.backend.GetVerbosityLevel();
                        result  = Code.Ok;
                        break;
                    }

                    case "setverbositylevel":
                    {
                        int vlev = int.Parse(pieces[2]);
                        content = string.Format("verbosity level was {0} now is set to {1}", this.backend.GetVerbosityLevel(), vlev);
                        this.backend.SetupVerbosityLevel(vlev);
                        result = Code.Ok;
                        break;
                    }

                    case "cleanuprsl":
                    {
                        RingMasterThreadPool.Instance.QueueUserWorkItem(_ =>
                            {
                                Thread.Sleep(10000);
                                Trace.TraceWarning("This instance will wipe its state {0} now", this.backend.GetType().Name);
                                this.backend.Factory.WipeAllDataAndShutdown();
                                Trace.TraceWarning("This instance will die now");
                                Environment.Exit(1);
                            });
                        content = "cleaning up RSL folder and restarting";
                        result  = Code.Ok;
                        break;
                    }

                    case "restart":
                    {
                        uint waitMS = 10000;

                        if (pieces.Length > 2)
                        {
                            if (!uint.TryParse(pieces[2], out waitMS))
                            {
                                waitMS = 10000;
                            }
                        }

                        // 2 minutes max wait
                        waitMS = Math.Min(waitMS, 120000);

                        RingMasterThreadPool.Instance.QueueUserWorkItem(_ =>
                            {
                                Thread.Sleep((int)waitMS);
                                Trace.TraceInformation("This instance will stop {0} now", this.backend.GetType().Name);
                                this.backend.Stop();

                                Thread.Sleep(2000);
                                Trace.TraceInformation("This instance will restart {0} now", this.backend.GetType().Name);
                                this.backend.Start(CancellationToken.None);
                            });

                        content = "restarted in " + waitMS + " miliseconds";
                        result  = Code.Ok;
                        break;
                    }

                    case "die":
                    {
                        RingMasterThreadPool.Instance.QueueUserWorkItem(_ =>
                            {
                                Thread.Sleep(10000);
                                Trace.TraceWarning("This instance will die now");
                                Environment.Exit(1);
                            });

                        if (pieces.Length > 2)
                        {
                            this.backend.FailoverInto(pieces[2]);
                        }

                        content = "die in 10 seconds";
                        result  = Code.Ok;
                        break;
                    }
                    }
                }

                return(result);
            }
Exemple #5
0
        /// <summary>
        /// Fixes child stats and node structures when data (byte[]) change
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="data">The data.</param>
        /// <param name="txtime">The tx_time.</param>
        /// <param name="path">The path.</param>
        /// <param name="xid">The tx_id.</param>
        /// <param name="locklist">The locklist for this session.</param>
        void IUnsafeTreeAccess.UnsafeSetData(IPersistedData node, byte[] data, long txtime, string path, long xid, ILockListTransaction locklist)
        {
            if (node == null)
            {
                throw new ArgumentNullException(nameof(node));
            }

            if (locklist == null && this.IsPathLockedDown(path))
            {
                throw new InvalidAclException(path, "lockdown");
            }

            if (ForceWB || locklist == null)
            {
                this.secondarypreprocessor.AppendSetData(node.Id, data, txtime, xid);
            }

            // It is possible for this setData to be a command for a replica.
            // if so, we will give it to the secondaryprocessor and let it decide what to do with it
            if (node.Name.Length >= 2 && node.Name[0] == '$' && node.Name[1] == '$' && this.secondarypreprocessor.ThisReplicaName != null)
            {
                this.secondarypreprocessor.TryRunCommand(node, data, txtime, xid);
            }

            locklist?.ValidateLockList(null, Perm.NONE, node, Perm.WRITE);
            int delta = -node.Stat.DataLength;

            if (data != null)
            {
                delta += data.Length;
            }

            this.UpdateStat(node, xid, txtime, ChangeKind.DataChanged, delta);
            node.Node.SetData(data);

            if (node.IsEphemeral)
            {
                this.EphemeralFactory.RecordDataDelta(delta);
            }
            else
            {
                if (locklist == null)
                {
                    this.Factory.RecordDataDelta(delta);
                }
                else
                {
                    locklist.RunOnCommit(() =>
                    {
                        this.Factory.RecordDataDelta(delta);
                    });
                }
            }

            node.Node.ScheduleTriggerWatchers(ChangeKind.DataChanged, path, locklist);
        }
Exemple #6
0
        /// <summary>
        /// Fixes parent stat and node structures when a child is added
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <param name="child">The child.</param>
        /// <param name="txtime">The tx_time.</param>
        /// <param name="path">The path.</param>
        /// <param name="xid">The tx_id.</param>
        /// <param name="locklist">The locklist for this session.</param>
        void IUnsafeTreeAccess.UnsafeAddChild(IPersistedData parent, IPersistedData child, long txtime, string path, long xid, ILockListTransaction locklist)
        {
            if (parent == null)
            {
                throw new ArgumentNullException(nameof(parent));
            }

            if (child == null)
            {
                throw new ArgumentNullException(nameof(child));
            }

            if (locklist == null && this.IsPathLockedDown(path))
            {
                throw new InvalidAclException(path, "lockdown");
            }

            if (ForceWB || locklist == null)
            {
                this.secondarypreprocessor.AppendAddChild(parent.Id, child.Id, txtime, xid);
            }

            locklist?.ValidateLockList(parent, Perm.CREATE, child, Perm.WRITE);

            this.UpdateStat(parent, xid, txtime, ChangeKind.ChildrenAdded);
            parent.Node.AddChild(child.Node);

            parent.Node.ScheduleTriggerWatchers(ChangeKind.ChildrenAdded, path, locklist);
        }
Exemple #7
0
        /// <summary>
        /// Fixes child stats and node structures when acl's change
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="list">The list.</param>
        /// <param name="txtime">The tx_time.</param>
        /// <param name="path">The path.</param>
        /// <param name="xid">The tx_id.</param>
        /// <param name="locklist">The locklist for this session.</param>
        void IUnsafeTreeAccess.UnsafeSetAcl(IPersistedData node, IReadOnlyList <Acl> list, long txtime, string path, long xid, ILockListTransaction locklist)
        {
            if (node == null)
            {
                throw new ArgumentNullException(nameof(node));
            }

            if (locklist == null && this.IsPathLockedDown(path))
            {
                throw new InvalidAclException(path, "lockdown");
            }

            if (ForceWB || locklist == null)
            {
                this.secondarypreprocessor.AppendSetAcl(node.Id, list, txtime, xid);
            }

            locklist?.ValidateLockList(null, Perm.NONE, node, Perm.WRITE);

            this.UpdateStat(node, xid, txtime, ChangeKind.AclChanged);
            node.Node.SetAcl(list);

            node.Node.ScheduleTriggerWatchers(ChangeKind.AclChanged, path, locklist);
        }
Exemple #8
0
        /// <summary>
        /// Fixes child stats and node structures when a node is created
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="txtime">The tx_time.</param>
        /// <param name="path">The path.</param>
        /// <param name="xid">The tx_id.</param>
        /// <param name="locklist">The locklist for this session.</param>
        void IUnsafeTreeAccess.UnsafeCreate(IPersistedData node, long txtime, string path, long xid, ILockListTransaction locklist)
        {
            if (locklist == null && this.IsPathLockedDown(path))
            {
                throw new InvalidAclException(path, "lockdown");
            }

            if (ForceWB || locklist == null)
            {
                this.secondarypreprocessor.AppendCreate(node, txtime, xid);
            }

            this.UpdateStat(node, xid, txtime, ChangeKind.NodeCreated);
            this.DoNodeForCreate(node);

            node.Node.ScheduleTriggerWatchers(ChangeKind.NodeCreated, path, locklist);
        }
Exemple #9
0
        /// <summary>
        /// Fixes parent and child stats and node structures when a child is removed
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <param name="node">The node.</param>
        /// <param name="txtime">The tx_time.</param>
        /// <param name="path">The path.</param>
        /// <param name="xid">The tx_id.</param>
        /// <param name="locklist">The locklist for this session.</param>
        /// <param name="triggerWatcher">Whether to trigger watcher</param>
        void IUnsafeTreeAccess.UnsafeDeleteNode(IPersistedData parent, IPersistedData node, long txtime, string path, long xid, ILockListTransaction locklist, bool triggerWatcher)
        {
            if (parent == null)
            {
                throw new ArgumentNullException(nameof(parent));
            }

            if (node == null)
            {
                throw new ArgumentNullException(nameof(node));
            }

            if (locklist == null && this.IsPathLockedDown(path))
            {
                throw new InvalidAclException(path, "lockdown");
            }

            if (ForceWB || locklist == null)
            {
                this.secondarypreprocessor.AppendDelete(parent.Id, node.Id, txtime, xid);
            }

            locklist?.ValidateLockList(parent, Perm.WRITE, node, Perm.WRITE);

            this.UpdateStat(node, xid, txtime, ChangeKind.NodeDeleted);
            if (node.IsEphemeral)
            {
                this.EphemeralFactory.Delete(node);
            }
            else
            {
                this.Factory.Delete(node);
            }

            if (triggerWatcher)
            {
                node.Node.ScheduleTriggerWatchers(ChangeKind.NodeDeleted, path, locklist);
            }
        }
Exemple #10
0
        /// <summary>
        /// Fixes parent and child stats and node structures when a child is removed
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <param name="node">The node.</param>
        /// <param name="txtime">The tx_time.</param>
        /// <param name="path">The path.</param>
        /// <param name="xid">The tx_id.</param>
        /// <param name="locklist">The locklist for this session.</param>
        /// <param name="triggerWatcher">Whether to trigger watcher</param>
        void IUnsafeTreeAccess.UnsafeRemoveChild(IPersistedData parent, IPersistedData node, long txtime, string path, long xid, ILockListTransaction locklist, bool triggerWatcher)
        {
            if (parent == null)
            {
                throw new ArgumentNullException(nameof(parent));
            }

            if (node == null)
            {
                throw new ArgumentNullException(nameof(node));
            }

            if (locklist == null && this.IsPathLockedDown(path))
            {
                throw new InvalidAclException(path, "lockdown");
            }

            if (ForceWB || locklist == null)
            {
                this.secondarypreprocessor.AppendRemoveChild(parent.Id, node.Id, txtime, xid);
            }

            locklist?.ValidateLockList(parent, Perm.WRITE, node, Perm.WRITE);

            this.UpdateStat(parent, xid, txtime, ChangeKind.ChildrenRemoved);

            parent.Node.RemoveChild(node.Name);

            if (triggerWatcher)
            {
                parent.Node.ScheduleTriggerWatchers(ChangeKind.ChildrenRemoved, this.GetParentPath(path), locklist);
            }
        }
Exemple #11
0
        /// <summary>
        /// Fixes parent and child stats and node structures when a child is removed
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <param name="node">The node.</param>
        /// <param name="txtime">The tx_time.</param>
        /// <param name="path">The path.</param>
        /// <param name="xid">The tx_id.</param>
        /// <param name="locklist">The locklist for this session.</param>
        void IUnsafeTreeAccess.UnsafeRemove(IPersistedData parent, IPersistedData node, long txtime, string path, long xid, ILockListTransaction locklist)
        {
            if (parent == null)
            {
                throw new ArgumentNullException(nameof(parent));
            }

            if (node == null)
            {
                throw new ArgumentNullException(nameof(node));
            }

            ((IUnsafeTreeAccess)this).UnsafeRemoveChild(parent, node, txtime, path, xid, locklist, false);
            ((IUnsafeTreeAccess)this).UnsafeDeleteNode(parent, node, txtime, path, xid, locklist, false);

            node.Node.ScheduleTriggerWatchers(ChangeKind.NodeDeleted, path, locklist);
            parent.Node.ScheduleTriggerWatchers(ChangeKind.ChildrenRemoved, this.GetParentPath(path), locklist);
        }