NOTE: Older SDKs may see the status code 5029 instead of 1001.
NOTE: This guide refers often to "issuing a Vivox SDK request," which accurately describes how Vivox actions are performed using the Vivox Core SDK. When using the Unity and Unreal SDKs, substitute this language with "calling a method in the Vivox plugin/package" in order to do something like sign into Vivox, join a voice/text channel, set your location in a Positional channel, or mute a user.
NOTE: For demonstration purposes, this guide references specific APIs belonging to the Vivox Unity SDK major version 15.x. However, the underlying concepts described all directly translate to the Vivox Unity 16.x SDK, Unreal SDK, and Core SDK, even though the public API differs from the examples used.
What is status code 1001?
The status code "Target Object Does Not Exist" (1001) occurs when an object handle that is used to refer to some SDK object in a subsequent request doesn't map to any known underlying object instance in the SDK. Depending on the context, the “target object” could be referring to something that isn't set up yet or anymore. This breaks down into three basic cases:
- A previous request did not succeed (or was never made)—meaning the concrete representation of the referenced object was never created.
- A previous request is still being processed—meaning the concrete representation of the referenced object is still being created or initialized, and isn't ready to be referenced yet.
- An intervening request (or event) resulted in (or signaled) the disconnection or deletion of some object—meaning the concrete representation of the referenced object is no longer around.
When and why does Case 1 occur?
In the first listed case where a previous request did not succeed, "Target Object Does Not Exist" (1001) typically results from improper error handling of either the return value when issuing the request, or of the status code delivered in the corresponding response callback. This can result in later requests referencing the handle for an object which was thought to be created but wasn’t, resulting in the 1001 status returned in the response callback for the second request.
A subset of the “never created” type is when the requisite request was simply never issued, rather than issued but never completed successfully. For instance, you must perform the initial login of a login session or initial connection of a channel session before calling most other methods on those objects (there are a few exceptions, such as local text-to-speech use cases). For instance, if you were to obtain a new IChannelSession
and call BeginSendText()
first before calling BeginConnect()
, you would get 1001 in the method’s AsyncCallback
when calling EndSendText()
.
When and why does Case 2 occur?
In the second listed case where a previous request is still being processed, a 1001 typically results from making a subsequent request too soon after a requisite prior request was made. For instance, the ChannelState
or TextState
of your IChannelSession
must be Connected
as the result of a previous BeginConnect()
call before a call to BeginSendText()
has a chance of succeeding, otherwise you could get 1001.
Critically, please note that just receiving a successful response in the AsyncCallback
does not necessarily mean that the request has been completely processed: it could still be too early! In particular, for network based requests made to establish a connection to Vivox servers, the callback only indicates that you have begun making the connection successfully, or in other words, that there were no invalid arguments or other impediments when issuing the request, and that the client will proceed in attempting to establish a connection. This is still useful info, because some subsequent requests only require you to be in the Connecting
state, whereas for others, you must further wait for the Connected
state. In the case of BeginSendText()
, calling it before the AsyncCallback
of BeginConnect()
will typically result in 1001, whereas calling it after the AsyncCallback
but before either TextState
or ChannelState
are Connected
will typically result in “Invalid State” (1019) instead.
When and why does Case 3 occur?
In the third listed case where the referenced object is no longer around, a 1001 typically results from making a subsequent request after an intervening request (or event) resulted in (or signaled) the deletion of that object. Unlike the previous two cases, this is not always preventable. A good example would be making a request to mute another channel participant after they’ve already left the channel, or in the case your game client receives an intervening event that the participant has left the channel after the request is made but before receiving the AsyncCallback
. Another example is when trying to disconnect from a channel that you’re no longer connected to (more on this later 🙂️). These cases might result in a 1001 status code in the AsyncCallback
.
Which “target objects” specifically should I be on the lookout for?
The most common “target objects” which might “not exist” are pretty simple: underlying representations of login sessions, channel sessions, and channel participants. In most cases, 1001 is completely preventable if you’re careful to avoid calling requests from non-Connected
login sessions or channel sessions except to connect them. That is, call BeginLogin()
first and wait for ILoginSession.State
to be Connected
, or call BeginConnect()
and wait for IChannelSession.ChannelState
to be Connected
before calling most other “Begin” methods in those classes, making sure to pay attention to any errors along the way.
[Ed. this paragraph only applies to Unity 15.x and Unreal SDKs] Note that leaving a channel using IChannelSession.Disconnect()
will keep the IChannelSession
in its parent ILoginSession
’s dictionary of ChannelSessions
in the Disconnected
state until removed with DeleteChannelSession()
. This is so that rejoining previous channels or regularly switching between two channels is easy without the cost of constructing new sessions each time with GetChannelSession()
. This is another easy scenario to accidentally get preventable 1001s, if any other “Begin” method is called on the Disconnected
session after Disconnect()
but before another BeginConnect()
call. If you would prefer disconnected channel sessions to be removed from the ChannelSessions
dictionary immediately on disconnect, DeleteChannelSession()
can be called directly on a Connected
channel and it will disconnect the channel before deleting, without the need to call Disconnect()
separately.
That said, not all instances of 1001 are preventable, such as a previously made request to act on a participant just as they leave. Unavoidable 1001 status codes might also occur in some cases of sudden network or CPU interruption after internet loss or app suspension/sleep, although recent Vivox SDKs are better equipped to properly notify apps of these situations and allow appropriate handling if the SDK is unable to recover on its own.
In which situations do the majority of preventable 1001s to occur?
In our experience, increases in both 1001 and 1019 ("Invalid State") tend to occur in subsequent requests which follow any error that results in the login process getting interrupted, as many implementations don't properly handle login failures in all cases, or don't wait for a success to make further requests. Failing to log in for any reason can lead to status 1001 in subsequent requests which require you to be logged in already, since they rely on referencing an underlying object that would’ve been created during a successful login (the "target object" which "does not exist"). The same goes for the channel connection process. Notably, login/join requests themselves typically do not result in the 1001 status.
However, because it’s possible with correct handling to prevent most 1001s from occurring it also makes sense that you might not experience a notable increase in 1001 alongside login or channel connection failures. If you do not, this is a good sign, because it suggests you’re generally handling the failures well, such as by retrying the failed login/join request at first, then eventually stopping without ever issuing SDK requests that require the login/join to have completed. Of course, you might still be encountering 1001 in other situations besides a failed login/join. If you're encountering 1001 in multiple types of requests, we recommend reviewing these incidents by request type and relating them to the three major cases outlined in the first section to see which areas you might be able to focus on for improvement.
Okay, but how do I handle them when they happen?
Now that we’ve covered when and why "Target Object Does Not Exist" (1001) status codes happen, and how to avoid them, here are some notes on how to handle 1001s when they do show up, as not all cases are preventable.
- For starters, preventable instances in cases 1 and 3 should never be retried, as they will always fail with 1001 again.
- Requests failing in case 2—where a previous action is still being processed and a later request fails with 1001 after being sent too early—can technically be retried and succeed, but it’s better just to just avoid the preventable failure in the first place by waiting for the appropriate
AsyncCallback
or property change to occur (such as aState
change toConnected
) before sending the request that is preventably failing with 1001. - Requests failing due to the unavoidable class of 1001 status codes mentioned in case 3 should not be retried, as they will also keep failing with 1001 like other case 3 instances.
- Basically, retrying will never work except for case 2, where a well-tuned implementation will avoid 1001 in the first place.
You keep saying “status code,” but aren’t these errors?
Not necessarily! One final important thing to keep in mind is that status code 1001 should not always be treated as an error. You might have noticed by now that this guide has been universally referring to these numbers as status codes instead of error codes. Occasionally, non-zero statuses can still be treated as successes, and 1001s which are part of case 3 (whether preventable or non-preventable) are a good example of this.
For instance, if you try to disconnect from a channel which you are already no longer connected to, the disconnect request will typically fail with "Target Object Does Not Exist" (1001), because the underlying channel object is already gone. A scenario like this is most likely to occur after a network or CPU interruption that the SDK fails to automatically recover from, resulting in an unexpected logout (one you did not initiate): in this case the local user is removed from all channels and logged out (and you are notified of this via various events and property changes).
If you tried to leave a channel in this state that you were previously joined to, you can expect 1001. However, since the desired state and actual state of not being in the channel are aligned, even though you “failed” to leave the channel (that you weren’t in), you can treat it as successful because you still end up not in the channel after the call. The worst assumption your implementation could make in this scenario is falsely thinking you are still in the channel because you received an “error” when trying to leave the channel (that you weren’t in in the first place). The same would be true when trying to logout a login session which is not logged in, or trying to kick a participant from a channel after they’ve already left the channel.
Similarly, while it’s not quite right to conceptually consider failures to locally mute or adjust the volume of participants no longer in a channel as “successes” exactly, the 1001 status in these cases can be safely ignored all the same, as performing the requested action is no longer relevant. Be on the lookout for these types of scenarios in your callback handling.