From d415661e35546a71605caf56463894536b29d735 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 18 Feb 2015 14:38:56 +0100 Subject: [PATCH] Add BundleAndDeferCallback to smack-tcp --- .../smack/tcp/BundleAndDefer.java | 39 +++++++++++++ .../smack/tcp/BundleAndDeferCallback.java | 49 ++++++++++++++++ .../smack/tcp/XMPPTCPConnection.java | 56 +++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/BundleAndDefer.java create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/BundleAndDeferCallback.java diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/BundleAndDefer.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/BundleAndDefer.java new file mode 100644 index 000000000..ad899f9e5 --- /dev/null +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/BundleAndDefer.java @@ -0,0 +1,39 @@ +/** + * + * Copyright 2015 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.tcp; + +import java.util.concurrent.atomic.AtomicBoolean; + + +public class BundleAndDefer { + + private final AtomicBoolean isStopped; + + BundleAndDefer(AtomicBoolean isStopped) { + this.isStopped = isStopped; + } + + public void stopCurrentBundleAndDefer() { + synchronized (isStopped) { + if (isStopped.get()) { + return; + } + isStopped.set(true); + isStopped.notify(); + } + } +} diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/BundleAndDeferCallback.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/BundleAndDeferCallback.java new file mode 100644 index 000000000..5102b0019 --- /dev/null +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/BundleAndDeferCallback.java @@ -0,0 +1,49 @@ +/** + * + * Copyright 2015 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.tcp; + +/** + * This callback is used to get the current value of the period in which Smack does bundle and defer + * outgoing stanzas. + *

+ * Smack will bundle and defer stanzas if the connection is authenticated, the send queue is empty + * and if a bundle and defer callback is set, either via + * {@link XMPPTCPConnection#setDefaultBundleAndDeferCallback(BundleAndDeferCallback)} or + * {@link XMPPTCPConnection#setBundleandDeferCallback(BundleAndDeferCallback)}, and + * {@link #getBundleAndDeferMillis(BundleAndDefer)} returns a positive value. In a mobile environment, bundling + * and deferring outgoing stanzas may reduce battery consumption. It heavily depends on the + * environment, but recommend values for the bundle and defer period range from 20-60 seconds. But + * keep in mind that longer periods decrease the realtime aspect of Smack. + *

+ *

+ * Smack will invoke the callback when it needs to know the length of the bundle and defer period. + * If {@link #getBundleAndDeferMillis(BundleAndDefer)} returns 0 or a negative value, then the + * stanzas will send immediately. You can also prematurely abort the bundling of stanzas by calling + * {@link BundleAndDefer#stopCurrentBundleAndDefer()}. + *

+ */ +public interface BundleAndDeferCallback { + + /** + * Return the bundle and defer period used by Smack in milliseconds. + * + * @param bundleAndDefer used to premature abort bundle and defer. + * @return the bundle and defer period in milliseconds. + */ + public int getBundleAndDeferMillis(BundleAndDefer bundleAndDefer); + +} diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index ae9610d5b..f7d98b6dd 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -125,6 +125,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -184,6 +185,10 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { private final SynchronizationPoint compressSyncPoint = new SynchronizationPoint( this); + private static BundleAndDeferCallback defaultBundleAndDeferCallback; + + private BundleAndDeferCallback bundleAndDeferCallback = defaultBundleAndDeferCallback; + private static boolean useSmDefault = false; private static boolean useSmResumptionDefault = true; @@ -1269,6 +1274,30 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { if (element == null) { continue; } + + // Get a local version of the bundle and defer callback, in case it's unset + // between the null check and the method invocation + final BundleAndDeferCallback localBundleAndDeferCallback = bundleAndDeferCallback; + // If the preconditions are given (e.g. bundleAndDefer callback is set, queue is + // empty), then we could wait a bit for further stanzas attempting to decrease + // our energy consumption + if (localBundleAndDeferCallback != null && isAuthenticated() && queue.isEmpty()) { + final AtomicBoolean bundlingAndDeferringStopped = new AtomicBoolean(); + final int bundleAndDeferMillis = localBundleAndDeferCallback.getBundleAndDeferMillis(new BundleAndDefer( + bundlingAndDeferringStopped)); + if (bundleAndDeferMillis > 0) { + long remainingWait = bundleAndDeferMillis; + final long waitStart = System.currentTimeMillis(); + synchronized (bundlingAndDeferringStopped) { + while (!bundlingAndDeferringStopped.get() && remainingWait > 0) { + bundlingAndDeferringStopped.wait(remainingWait); + remainingWait = bundleAndDeferMillis + - (System.currentTimeMillis() - waitStart); + } + } + } + } + Stanza packet = null; if (element instanceof Stanza) { packet = (Stanza) element; @@ -1709,4 +1738,31 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { serverHandledStanzasCount = handledCount; } + + /** + * Set the default bundle and defer callback used for new connections. + * + * @param defaultBundleAndDeferCallback + * @see BundleAndDeferCallback + * @since 4.1 + */ + public static void setDefaultBundleAndDeferCallback(BundleAndDeferCallback defaultBundleAndDeferCallback) { + XMPPTCPConnection.defaultBundleAndDeferCallback = defaultBundleAndDeferCallback; + } + + /** + * Set the bundle and defer callback used for this connection. + *

+ * You can use null as argument to reset the callback. Outgoing stanzas will then + * no longer get deferred. + *

+ * + * @param bundleAndDeferCallback the callback or null. + * @see BundleAndDeferCallback + * @since 4.1 + */ + public void setBundleandDeferCallback(BundleAndDeferCallback bundleAndDeferCallback) { + this.bundleAndDeferCallback = bundleAndDeferCallback; + } + }